tenets.core.analysis.implementations¶
tenets.core.analysis.implementations¶
Language-specific code analyzers.
This package contains implementations of language analyzers for various programming languages. Each analyzer provides language-specific parsing and analysis capabilities.
Available analyzers: - PythonAnalyzer: Python code analysis with AST parsing - JavaScriptAnalyzer: JavaScript/TypeScript analysis - JavaAnalyzer: Java code analysis - GoAnalyzer: Go language analysis - RustAnalyzer: Rust code analysis - CppAnalyzer: C/C++ code analysis - CSharpAnalyzer: C# code analysis - SwiftAnalyzer: Swift code analysis - RubyAnalyzer: Ruby code analysis - PhpAnalyzer: PHP code analysis - KotlinAnalyzer: Kotlin code analysis - ScalaAnalyzer: Scala code analysis - DartAnalyzer: Dart code analysis - GDScriptAnalyzer: GDScript (Godot) analysis - HTMLAnalyzer: HTML markup analysis - CSSAnalyzer: CSS stylesheet analysis - GenericAnalyzer: Fallback for unsupported languages
CppAnalyzer¶
Bases: LanguageAnalyzer
C/C++ code analyzer.
Provides analysis for C and C++ files including: - Include directive analysis (system and local) - Class, struct, and union extraction - Template analysis - Function and method extraction - Namespace handling - Macro and preprocessor directive analysis - Modern C++ features (auto, lambdas, smart pointers) - STL usage detection - Memory management patterns
Supports both C and C++ with appropriate feature detection.
Initialize the C++ analyzer with logger.
Source code in tenets/core/analysis/implementations/cpp_analyzer.py
extract_imports¶
Extract includes from C/C++ code.
Handles: - System includes: #include
| PARAMETER | DESCRIPTION |
|---|---|
content | C/C++ source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[ImportInfo] | List of ImportInfo objects representing includes |
Source code in tenets/core/analysis/implementations/cpp_analyzer.py
def extract_imports(self, content: str, file_path: Path) -> List[ImportInfo]:
"""Extract includes from C/C++ code.
Handles:
- System includes: #include <iostream>
- Local includes: #include "myheader.h"
- Conditional includes with #ifdef
- Include guards
Args:
content: C/C++ source code
file_path: Path to the file being analyzed
Returns:
List of ImportInfo objects representing includes
"""
imports = []
lines = content.split("\n")
# Track preprocessor state
ifdef_stack = []
current_condition = True
for i, line in enumerate(lines, 1):
stripped = line.strip()
# Handle preprocessor conditionals
if stripped.startswith("#ifdef") or stripped.startswith("#ifndef"):
condition = stripped.split()[1] if len(stripped.split()) > 1 else ""
ifdef_stack.append(current_condition)
# We'll track all includes regardless of conditionals for analysis
continue
elif stripped.startswith("#if"):
ifdef_stack.append(current_condition)
continue
elif stripped.startswith("#else"):
if ifdef_stack:
current_condition = not current_condition
continue
elif stripped.startswith("#elif"):
continue
elif stripped.startswith("#endif"):
if ifdef_stack:
current_condition = ifdef_stack.pop()
continue
# System includes
system_include = re.match(r"^\s*#\s*include\s*<([^>]+)>", line)
if system_include:
header = system_include.group(1)
imports.append(
ImportInfo(
module=header,
line=i,
type="system",
is_relative=False,
is_stdlib=self._is_stdlib_header(header),
is_stl=self._is_stl_header(header),
conditional=len(ifdef_stack) > 0,
)
)
continue
# Local includes
local_include = re.match(r'^\s*#\s*include\s*"([^"]+)"', line)
if local_include:
header = local_include.group(1)
imports.append(
ImportInfo(
module=header,
line=i,
type="local",
is_relative=True,
is_project_header=True,
conditional=len(ifdef_stack) > 0,
)
)
continue
# Detect include guards
self._detect_include_guards(content, imports)
return imports
extract_exports¶
Extract exported symbols from C/C++ code.
In C/C++, symbols are exported by default unless static. For headers, we extract declarations. For source files, we extract non-static definitions.
| PARAMETER | DESCRIPTION |
|---|---|
content | C/C++ source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[Dict[str, Any]] | List of exported symbols |
Source code in tenets/core/analysis/implementations/cpp_analyzer.py
def extract_exports(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
"""Extract exported symbols from C/C++ code.
In C/C++, symbols are exported by default unless static.
For headers, we extract declarations. For source files,
we extract non-static definitions.
Args:
content: C/C++ source code
file_path: Path to the file being analyzed
Returns:
List of exported symbols
"""
exports = []
is_header = file_path.suffix in [".h", ".hh", ".hpp", ".hxx", ".h++"]
# Extract namespace if present
namespace = self._extract_namespace(content)
# Non-static functions
func_pattern = r"^(?:template\s*<[^>]*>\s*)?(?!static)(?:(?:inline|extern|virtual|explicit|constexpr)\s+)*(?:[\w\s\*&:<>]+)\s+(\w+)\s*\([^)]*\)(?:\s*const)?(?:\s*noexcept)?(?:\s*override)?(?:\s*final)?(?:\s*=\s*0)?(?:\s*(?:\{|;))"
for match in re.finditer(func_pattern, content, re.MULTILINE):
func_name = match.group(1)
# Filter out keywords
if func_name not in [
"if",
"for",
"while",
"switch",
"return",
"delete",
"new",
"throw",
"catch",
]:
line_content = content[match.start() : match.end()]
before_window = content[max(0, match.start() - 200) : match.start()]
is_tmpl = (
("template" in line_content)
or ("template" in before_window)
or self._is_template_function(content, match.start())
)
exports.append(
{
"name": func_name,
"type": "function",
"line": content[: match.start()].count("\n") + 1,
"namespace": namespace,
"is_inline": "inline" in line_content,
"is_virtual": "virtual" in line_content,
"is_pure_virtual": "= 0" in line_content,
"is_constexpr": "constexpr" in line_content,
"is_template": is_tmpl,
}
)
# Classes and structs (public by default in struct)
class_pattern = r"\b(?:struct|(?<!enum\s)class)\s+(?:__declspec\([^)]+\)\s+)?(\w+)(?:\s*:\s*(?:public|private|protected)\s+[\w:]+)?(?:\s*\{|;)"
for match in re.finditer(class_pattern, content):
class_name = match.group(1)
is_struct = "struct" in match.group(0)
# Find keyword position for accurate template check
inner = match.group(0)
kw = "struct" if "struct" in inner else "class"
kw_pos = match.start() + inner.find(kw)
exports.append(
{
"name": class_name,
"type": "struct" if is_struct else "class",
"line": content[: match.start()].count("\n") + 1,
"namespace": namespace,
"default_visibility": "public" if is_struct else "private",
"is_template": self._is_template_class(content, kw_pos),
}
)
# Enums
enum_pattern = r"\benum\s+(?:class\s+)?(\w+)(?:\s*:\s*\w+)?(?:\s*\{|;)"
for match in re.finditer(enum_pattern, content):
enum_name = match.group(1)
is_enum_class = "enum class" in match.group(0)
exports.append(
{
"name": enum_name,
"type": "enum_class" if is_enum_class else "enum",
"line": content[: match.start()].count("\n") + 1,
"namespace": namespace,
}
)
# Unions
union_pattern = r"\bunion\s+(\w+)(?:\s*\{|;)"
for match in re.finditer(union_pattern, content):
exports.append(
{
"name": match.group(1),
"type": "union",
"line": content[: match.start()].count("\n") + 1,
"namespace": namespace,
}
)
# Typedefs and using declarations
typedef_pattern = r"\btypedef\s+.*?\s+(\w+)\s*;"
for match in re.finditer(typedef_pattern, content):
exports.append(
{
"name": match.group(1),
"type": "typedef",
"line": content[: match.start()].count("\n") + 1,
"namespace": namespace,
}
)
using_pattern = r"\busing\s+(\w+)\s*="
for match in re.finditer(using_pattern, content):
exports.append(
{
"name": match.group(1),
"type": "using_alias",
"line": content[: match.start()].count("\n") + 1,
"namespace": namespace,
}
)
# Global variables (non-static)
if not is_header:
var_pattern = (
r"^(?!static)(?:extern\s+)?(?:const\s+)?(?:[\w\s\*&:<>]+)\s+(\w+)\s*(?:=|;)"
)
for match in re.finditer(var_pattern, content, re.MULTILINE):
var_name = match.group(1)
if var_name not in [
"if",
"for",
"while",
"return",
"class",
"struct",
"enum",
"typedef",
"using",
]:
exports.append(
{
"name": var_name,
"type": "variable",
"line": content[: match.start()].count("\n") + 1,
"namespace": namespace,
"is_const": "const" in match.group(0),
"is_extern": "extern" in match.group(0),
}
)
return exports
extract_structure¶
Extract code structure from C/C++ file.
Extracts: - Namespaces - Classes and structs with inheritance - Functions and methods - Templates - Macros and preprocessor directives - Global variables - Operator overloads
| PARAMETER | DESCRIPTION |
|---|---|
content | C/C++ source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
CodeStructure | CodeStructure object with extracted elements |
Source code in tenets/core/analysis/implementations/cpp_analyzer.py
def extract_structure(self, content: str, file_path: Path) -> CodeStructure:
"""Extract code structure from C/C++ file.
Extracts:
- Namespaces
- Classes and structs with inheritance
- Functions and methods
- Templates
- Macros and preprocessor directives
- Global variables
- Operator overloads
Args:
content: C/C++ source code
file_path: Path to the file being analyzed
Returns:
CodeStructure object with extracted elements
"""
structure = CodeStructure()
# Determine if it's C or C++
is_cpp = self._is_cpp_file(file_path, content)
structure.language_variant = "C++" if is_cpp else "C"
# Extract namespaces (C++ only)
if is_cpp:
namespace_pattern = r"namespace\s+(\w+)\s*\{"
for match in re.finditer(namespace_pattern, content):
structure.namespaces.append(
{"name": match.group(1), "line": content[: match.start()].count("\n") + 1}
)
# Extract classes and structs
class_pattern = r"(?:template\s*<[^>]+>\s*)?(?:struct|(?<!enum\s)class)\s+(\w+)(?:\s*:\s*((?:public|private|protected)\s+[\w:]+(?:\s*,\s*(?:public|private|protected)\s+[\w:]+)*))?"
for match in re.finditer(class_pattern, content):
class_name = match.group(1)
inheritance = match.group(2)
# Parse inheritance
bases = []
if inheritance:
for base in inheritance.split(","):
base = base.strip()
# Remove access specifier
base = re.sub(r"^(public|private|protected)\s+", "", base)
bases.append(base)
# Find class body
class_start = match.end()
class_body = self._extract_class_body(content, class_start)
# Extract methods and members
methods = []
fields = []
if class_body:
methods = self._extract_class_methods(class_body)
fields = self._extract_class_fields(class_body)
inner = match.group(0)
kw = "struct" if "struct" in inner else "class"
kw_pos = match.start() + inner.find(kw)
class_info = ClassInfo(
name=class_name,
line=content[: match.start()].count("\n") + 1,
bases=bases,
methods=methods,
fields=fields,
is_struct="struct" in match.group(0),
is_template=self._is_template_class(content, kw_pos),
)
structure.classes.append(class_info)
# Extract standalone functions
func_pattern = r"(?:template\s*<[^>]+>\s*)?(?:(?:inline|static|extern|virtual|explicit|constexpr)\s+)*(?:[\w\s\*&:<>]+)\s+(\w+)\s*\([^)]*\)(?:\s*const)?(?:\s*noexcept)?(?:\s*\{|;)"
for match in re.finditer(func_pattern, content, re.MULTILINE):
func_name = match.group(1)
# Filter out keywords and methods
if func_name in [
"if",
"for",
"while",
"switch",
"return",
"delete",
"new",
"throw",
"catch",
]:
continue
# Check if it's inside a class (simple heuristic)
if self._is_inside_class(content, match.start()):
continue
func_info = FunctionInfo(
name=func_name,
line=content[: match.start()].count("\n") + 1,
is_static="static" in match.group(0),
is_inline="inline" in match.group(0),
is_constexpr="constexpr" in match.group(0),
is_template="template" in content[max(0, match.start() - 100) : match.start()],
is_exported="static" not in match.group(0),
)
structure.functions.append(func_info)
# Extract templates
template_pattern = r"template\s*<([^>]+)>\s*(?:class|struct|typename|function)\s+(\w+)"
for match in re.finditer(template_pattern, content):
structure.templates.append(
{
"name": match.group(2),
"parameters": match.group(1),
"line": content[: match.start()].count("\n") + 1,
}
)
# Extract macros
macro_pattern = r"^\s*#define\s+(\w+)(?:\([^)]*\))?"
for match in re.finditer(macro_pattern, content, re.MULTILINE):
macro_name = match.group(1)
is_function_macro = "(" in match.group(0)
structure.macros.append(
{
"name": macro_name,
"line": content[: match.start()].count("\n") + 1,
"is_function_macro": is_function_macro,
}
)
# Extract global variables
global_var_pattern = (
r"^(?:static\s+)?(?:const\s+)?(?:[\w\s\*&:<>]+)\s+(\w+)\s*(?:=\s*[^;]+)?\s*;"
)
for match in re.finditer(global_var_pattern, content, re.MULTILINE):
var_name = match.group(1)
# Filter out function declarations and keywords
if var_name in ["if", "for", "while", "return", "class", "struct", "enum", "typedef"]:
continue
if not self._is_inside_class(content, match.start()) and not self._is_inside_function(
content, match.start()
):
structure.variables.append(
{
"name": var_name,
"line": content[: match.start()].count("\n") + 1,
"type": "global",
"is_static": "static" in match.group(0),
"is_const": "const" in match.group(0),
}
)
# Extract unions
union_pattern = r"union\s+(\w+)\s*\{"
for match in re.finditer(union_pattern, content):
structure.unions.append(
{"name": match.group(1), "line": content[: match.start()].count("\n") + 1}
)
# Extract operator overloads
operator_pattern = r"operator\s*(?:[\+\-\*\/\%\^\&\|\~\!\=\<\>\[\]\(\)]|\+\+|\-\-|\<\<|\>\>|\=\=|\!\=|\<\=|\>\=|\&\&|\|\||\+\=|\-\=|\*\=|\/\=|\%\=|\^\=|\&\=|\|\=|\<\<\=|\>\>\=|,|->\*?|new|delete)(?:\s*\[\])?"
operator_count = len(re.findall(operator_pattern, content))
structure.operator_overloads = operator_count
# Detect STL usage (boolean for test compatibility)
stl_types_found = self._detect_stl_usage(content)
structure.uses_stl = bool(stl_types_found)
structure.stl_types = stl_types_found # Optionally keep the list for other uses
# Detect smart pointers
structure.smart_pointers = self._detect_smart_pointers(content)
# Count lambda expressions
lambda_pattern = r"\[[^\]]*\]\s*\([^)]*\)\s*(?:->[\w\s]+)?\s*\{"
structure.lambda_count = len(re.findall(lambda_pattern, content))
return structure
calculate_complexity¶
Calculate complexity metrics for C/C++ code.
Calculates: - Cyclomatic complexity - Cognitive complexity - Preprocessor complexity - Template complexity - Memory management complexity
| PARAMETER | DESCRIPTION |
|---|---|
content | C/C++ source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
ComplexityMetrics | ComplexityMetrics object with calculated metrics |
Source code in tenets/core/analysis/implementations/cpp_analyzer.py
def calculate_complexity(self, content: str, file_path: Path) -> ComplexityMetrics:
"""Calculate complexity metrics for C/C++ code.
Calculates:
- Cyclomatic complexity
- Cognitive complexity
- Preprocessor complexity
- Template complexity
- Memory management complexity
Args:
content: C/C++ source code
file_path: Path to the file being analyzed
Returns:
ComplexityMetrics object with calculated metrics
"""
metrics = ComplexityMetrics()
# Calculate cyclomatic complexity
complexity = 1
decision_keywords = [
r"\bif\b",
r"\belse\s+if\b",
r"\belse\b",
r"\bfor\b",
r"\bwhile\b",
r"\bdo\b",
r"\bswitch\b",
r"\bcase\b",
r"\bcatch\b",
r"\b&&\b",
r"\|\|",
r"\?",
]
for keyword in decision_keywords:
complexity += len(re.findall(keyword, content))
metrics.cyclomatic = complexity
# Calculate cognitive complexity
cognitive = 0
nesting_level = 0
max_nesting = 0
lines = content.split("\n")
for line in lines:
# Skip comments and preprocessor directives
if (
line.strip().startswith("//")
or line.strip().startswith("/*")
or line.strip().startswith("#")
):
continue
# Track nesting
opening_braces = line.count("{")
closing_braces = line.count("}")
nesting_level += opening_braces - closing_braces
max_nesting = max(max_nesting, nesting_level)
# Control structures with nesting penalty
control_patterns = [
(r"\bif\b", 1),
(r"\bfor\b", 1),
(r"\bwhile\b", 1),
(r"\bswitch\b", 1),
(r"\btry\b", 1),
(r"\bcatch\b", 1),
]
for pattern, weight in control_patterns:
if re.search(pattern, line):
cognitive += weight * (1 + max(0, nesting_level - 1))
metrics.cognitive = cognitive
metrics.max_depth = max_nesting
# Count code elements
metrics.line_count = len(lines)
metrics.code_lines = self._count_code_lines(content)
metrics.comment_lines = self._count_comment_lines(content)
metrics.comment_ratio = (
metrics.comment_lines / metrics.line_count if metrics.line_count > 0 else 0
)
# Count functions
metrics.function_count = len(re.findall(r"[\w\s\*&:<>]+\s+\w+\s*\([^)]*\)\s*\{", content))
# Count classes and structs
metrics.class_count = len(re.findall(r"\b(?:class|struct)\s+\w+", content))
# Template metrics
metrics.template_count = len(re.findall(r"template\s*<", content))
metrics.template_specializations = len(re.findall(r"template\s*<>", content))
# Preprocessor metrics
metrics.macro_count = len(re.findall(r"^\s*#define\s+", content, re.MULTILINE))
metrics.ifdef_count = len(re.findall(r"^\s*#if(?:def|ndef)?\s+", content, re.MULTILINE))
metrics.include_count = len(re.findall(r"^\s*#include\s+", content, re.MULTILINE))
# Memory management metrics
metrics.new_count = len(re.findall(r"\bnew\s+", content))
# Count delete and delete[]
metrics.delete_count = len(re.findall(r"\bdelete\s*(?:\[\])?", content))
metrics.malloc_count = len(re.findall(r"\bmalloc\s*\(", content))
metrics.free_count = len(re.findall(r"\bfree\s*\(", content))
# Smart pointer usage (count both types and factory helpers)
metrics.unique_ptr_count = len(re.findall(r"\bunique_ptr\s*<", content)) + len(
re.findall(r"(?:\b[\w:]+::)?make_unique(?:\s*<[^>]+>)?\s*\(", content)
)
metrics.shared_ptr_count = len(re.findall(r"\bshared_ptr\s*<", content)) + len(
re.findall(r"(?:\b[\w:]+::)?make_shared(?:\s*<[^>]+>)?\s*\(", content)
)
metrics.weak_ptr_count = len(re.findall(r"\bweak_ptr\s*<", content))
# RAII indicators
metrics.uses_raii = (
metrics.unique_ptr_count > 0 or metrics.shared_ptr_count > 0 or "RAII" in content
)
# Calculate memory safety score
manual_memory = (
metrics.new_count + metrics.delete_count + metrics.malloc_count + metrics.free_count
)
smart_memory = metrics.unique_ptr_count + metrics.shared_ptr_count
if manual_memory + smart_memory > 0:
metrics.memory_safety_score = smart_memory / (manual_memory + smart_memory)
else:
metrics.memory_safety_score = 1.0
# Calculate maintainability index
if metrics.code_lines > 0:
# Adjusted for C++ complexity
template_factor = 1 - (metrics.template_count * 0.02)
memory_factor = metrics.memory_safety_score
mi = (
171
- 5.2 * math.log(max(1, complexity))
- 0.23 * complexity
- 16.2 * math.log(metrics.code_lines)
+ 10 * template_factor
+ 15 * memory_factor
)
metrics.maintainability_index = max(0, min(100, mi))
return metrics
CSharpAnalyzer¶
Bases: LanguageAnalyzer
C# code analyzer with Unity3D support.
Provides comprehensive analysis for C# files including: - Using directives and namespace analysis - Class, interface, struct, enum, and record extraction - Property and event analysis - Async/await and Task-based patterns - LINQ query detection - Attribute processing - Unity3D specific patterns (MonoBehaviour, Coroutines, etc.) - .NET Framework/Core detection - Nullable reference types (C# 8+) - Pattern matching (C# 7+)
Supports modern C# features and Unity3D development patterns.
Initialize the C# analyzer with logger.
Source code in tenets/core/analysis/implementations/csharp_analyzer.py
extract_imports¶
Extract using directives from C# code.
Handles: - using statements: using System.Collections.Generic; - using static: using static System.Math; - using aliases: using Project = PC.MyCompany.Project; - global using (C# 10+): global using System.Text; - Unity-specific usings
| PARAMETER | DESCRIPTION |
|---|---|
content | C# source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[ImportInfo] | List of ImportInfo objects with import details |
Source code in tenets/core/analysis/implementations/csharp_analyzer.py
def extract_imports(self, content: str, file_path: Path) -> List[ImportInfo]:
"""Extract using directives from C# code.
Handles:
- using statements: using System.Collections.Generic;
- using static: using static System.Math;
- using aliases: using Project = PC.MyCompany.Project;
- global using (C# 10+): global using System.Text;
- Unity-specific usings
Args:
content: C# source code
file_path: Path to the file being analyzed
Returns:
List of ImportInfo objects with import details
"""
imports: List[ImportInfo] = []
lines = content.splitlines()
current_namespace: Optional[str] = None
seen_code = False # stop parsing usings after first non-using code element at top-level
# Pre-compile patterns (hot path in large files)
namespace_re = re.compile(r"^\s*namespace\s+([\w\.]+)")
alias_re = re.compile(r"^\s*(?:(global)\s+)?using\s+([\w\.]+)\s*=\s*([^;]+?)\s*;")
using_re = re.compile(r"^\s*(?:(global)\s+)?using\s+(?:(static)\s+)?([\w\.]+)\s*;")
decl_re = re.compile(
r"^\s*(?:public\s+)?(?:partial\s+)?(?:abstract\s+)?(?:sealed\s+)?(?:class|interface|struct|enum|delegate|record)\b"
)
for i, line in enumerate(lines, 1):
stripped = line.strip()
if not stripped:
continue
# Skip single-line comments
if stripped.startswith("//"):
continue
# Namespace (track for nested usings)
m = namespace_re.match(line)
if m:
current_namespace = m.group(1)
# Don't treat namespace declaration itself as code for stopping further usings
continue
# Stop scanning after first real code (class/interface/etc.) at top-level
if decl_re.match(line):
seen_code = True
if seen_code:
# Still allow usings inside namespace blocks (indented) – C# allows that
# Only break if this is a top-level code declaration and not inside a namespace context yet
if current_namespace is None:
break
# Using alias
m = alias_re.match(line)
if m:
is_global = m.group(1) == "global"
alias = m.group(2)
target = m.group(3).strip()
base_for_category = target.split("<", 1)[0].strip()
category = self._categorize_import(base_for_category)
is_unity = self._is_unity_import(base_for_category)
imports.append(
ImportInfo(
module=target,
alias=alias,
line=i,
type="global_using_alias" if is_global else "using_alias",
is_relative=False,
category=category,
is_unity=is_unity,
namespace_context=current_namespace,
)
)
continue
# Standard / static / global usings
m = using_re.match(line)
if m:
is_global = m.group(1) == "global"
is_static = m.group(2) == "static"
ns = m.group(3)
category = self._categorize_import(ns)
is_unity = self._is_unity_import(ns)
if is_global:
import_type = "global_using"
elif is_static:
import_type = "using_static"
else:
import_type = "using"
imports.append(
ImportInfo(
module=ns,
line=i,
type=import_type,
is_relative=False,
category=category,
is_unity=is_unity,
namespace_context=current_namespace,
)
)
continue
# .csproj dependency parsing
if file_path.suffix.lower() == ".csproj":
imports.extend(self._extract_csproj_dependencies(content))
return imports
extract_exports¶
Extract public members from C# code.
In C#, public members are accessible from other assemblies. This includes public classes, interfaces, structs, enums, delegates, etc.
| PARAMETER | DESCRIPTION |
|---|---|
content | C# source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[Dict[str, Any]] | List of exported (public) symbols |
Source code in tenets/core/analysis/implementations/csharp_analyzer.py
def extract_exports(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
"""Extract public members from C# code.
In C#, public members are accessible from other assemblies.
This includes public classes, interfaces, structs, enums, delegates, etc.
Args:
content: C# source code
file_path: Path to the file being analyzed
Returns:
List of exported (public) symbols
"""
exports = []
# Extract namespace
namespace_match = re.search(r"^\s*namespace\s+([\w\.]+)", content, re.MULTILINE)
namespace = namespace_match.group(1) if namespace_match else ""
# Public classes (including Unity MonoBehaviours)
class_pattern = r"^\s*(?:public\s+)?(?:partial\s+)?(?:abstract\s+)?(?:sealed\s+)?(?:static\s+)?class\s+(\w+)(?:\s*:\s*([\w\.,\s]+))?"
for match in re.finditer(class_pattern, content, re.MULTILINE):
class_name = match.group(1)
inheritance = match.group(2)
modifiers = []
if "abstract" in match.group(0):
modifiers.append("abstract")
if "sealed" in match.group(0):
modifiers.append("sealed")
if "static" in match.group(0):
modifiers.append("static")
if "partial" in match.group(0):
modifiers.append("partial")
# Check if it's a Unity component
is_unity_component = False
unity_base_class = None
if inheritance:
if "MonoBehaviour" in inheritance:
is_unity_component = True
unity_base_class = "MonoBehaviour"
elif "ScriptableObject" in inheritance:
is_unity_component = True
unity_base_class = "ScriptableObject"
elif "Editor" in inheritance:
is_unity_component = True
unity_base_class = "Editor"
exports.append(
{
"name": class_name,
"type": "class",
"line": content[: match.start()].count("\n") + 1,
"namespace": namespace,
"modifiers": modifiers,
"inheritance": inheritance,
"is_unity_component": is_unity_component,
"unity_base_class": unity_base_class,
}
)
# Public interfaces
interface_pattern = r"^\s*(?:public\s+)?(?:partial\s+)?interface\s+(\w+)(?:<[^>]+>)?(?:\s*:\s*([\w\.,\s]+))?"
for match in re.finditer(interface_pattern, content, re.MULTILINE):
exports.append(
{
"name": match.group(1),
"type": "interface",
"line": content[: match.start()].count("\n") + 1,
"namespace": namespace,
"extends": match.group(2),
}
)
# Public structs
struct_pattern = r"^\s*(?:public\s+)?(?:readonly\s+)?(?:ref\s+)?struct\s+(\w+)"
for match in re.finditer(struct_pattern, content, re.MULTILINE):
modifiers = []
if "readonly" in match.group(0):
modifiers.append("readonly")
if "ref" in match.group(0):
modifiers.append("ref")
exports.append(
{
"name": match.group(1),
"type": "struct",
"line": content[: match.start()].count("\n") + 1,
"namespace": namespace,
"modifiers": modifiers,
}
)
# Public enums (support both 'enum' and 'enum class' styles)
enum_pattern = r"^\s*(?:public\s+)?enum(?:\s+class)?\s+(\w+)(?:\s*:\s*([\w\.]+))?"
for match in re.finditer(enum_pattern, content, re.MULTILINE):
enum_type = "enum_class" if "enum class" in match.group(0) else "enum"
exports.append(
{
"name": match.group(1),
"type": enum_type,
"line": content[: match.start()].count("\n") + 1,
"namespace": namespace,
"base_type": match.group(2),
}
)
# Public delegates
delegate_pattern = r"^\s*(?:public\s+)?delegate\s+(\w+)\s+(\w+(?:<[^>]+>)?)\s*\([^)]*\)"
for match in re.finditer(delegate_pattern, content, re.MULTILINE):
exports.append(
{
"name": match.group(2),
"type": "delegate",
"return_type": match.group(1),
"line": content[: match.start()].count("\n") + 1,
"namespace": namespace,
}
)
# Public records (C# 9+)
record_pattern = r"^\s*(?:public\s+)?record\s+(?:class\s+|struct\s+)?(\w+)"
for match in re.finditer(record_pattern, content, re.MULTILINE):
record_type = "record_struct" if "struct" in match.group(0) else "record"
exports.append(
{
"name": match.group(1),
"type": record_type,
"line": content[: match.start()].count("\n") + 1,
"namespace": namespace,
}
)
return exports
extract_structure¶
Extract code structure from C# file.
Extracts: - Namespace declarations - Classes with inheritance and interfaces - Properties with getters/setters - Methods including async methods - Events and delegates - Unity-specific components (MonoBehaviours, Coroutines) - LINQ queries - Attributes
| PARAMETER | DESCRIPTION |
|---|---|
content | C# source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
CodeStructure | CodeStructure object with extracted elements |
Source code in tenets/core/analysis/implementations/csharp_analyzer.py
def extract_structure(self, content: str, file_path: Path) -> CodeStructure:
"""Extract code structure from C# file.
Extracts:
- Namespace declarations
- Classes with inheritance and interfaces
- Properties with getters/setters
- Methods including async methods
- Events and delegates
- Unity-specific components (MonoBehaviours, Coroutines)
- LINQ queries
- Attributes
Args:
content: C# source code
file_path: Path to the file being analyzed
Returns:
CodeStructure object with extracted elements
"""
structure = CodeStructure()
# Extract namespace
namespace_match = re.search(r"^\s*namespace\s+([\w\.]+)", content, re.MULTILINE)
if namespace_match:
structure.namespace = namespace_match.group(1)
# Detect if it's a Unity script
structure.is_unity_script = self._is_unity_script(content)
# Extract classes
# Capture any stacked attribute blocks immediately preceding the class declaration in a named group
# so we don't rely on a fragile backward scan that fails when the regex itself already consumed them.
class_pattern = (
r"(?:^|\n)\s*(?P<attr_block>(?:\[[^\]]+\]\s*)*)"
r"(?:(?P<visibility>public|private|protected|internal)\s+)?"
r"(?:(?P<partial>partial)\s+)?(?:(?P<abstract>abstract)\s+)?(?:(?P<sealed>sealed)\s+)?(?:(?P<static>static)\s+)?"
r"class\s+(?P<class_name>\w+)(?:<(?P<generics>[^>]+)>)?(?:\s*:\s*(?P<inheritance>[\w\.,\s<>]+))?"
)
for match in re.finditer(class_pattern, content):
attr_block = match.group("attr_block") or ""
class_name = match.group("class_name") or ""
generics = match.group("generics")
inheritance = match.group("inheritance")
# Prefer directly captured attribute block; fallback to legacy backward scan only if empty
attributes = self._extract_attributes(attr_block) if attr_block else []
if not attributes:
# Legacy backward scan (kept for robustness in edge cases where regex miss might occur)
start_line_index = content[: match.start()].count("\n")
lines = content.splitlines()
attr_lines: List[str] = []
line_cursor = start_line_index - 1
while line_cursor >= 0:
line_text = lines[line_cursor].strip()
if not line_text or not line_text.startswith("["):
break
attr_lines.insert(0, line_text)
line_cursor -= 1
if attr_lines:
attributes = self._extract_attributes("\n".join(attr_lines))
# Collect modifiers
modifiers: List[str] = []
for key in ["partial", "abstract", "sealed", "static"]:
if match.group(key):
modifiers.append(match.group(key))
visibility = match.group("visibility") or None
# Parse inheritance
bases = []
interfaces = []
is_monobehaviour = False
is_scriptable_object = False
if inheritance:
for item in inheritance.split(","):
item = item.strip()
if item == "MonoBehaviour":
is_monobehaviour = True
bases.append(item)
elif item == "ScriptableObject":
is_scriptable_object = True
bases.append(item)
elif item.startswith("I"): # Convention for interfaces
interfaces.append(item)
else:
bases.append(item)
# Find class body
class_body = self._extract_class_body(content, match.end())
# Extract class components
methods = []
properties = []
fields = []
events = []
unity_methods = []
coroutines = []
if class_body:
methods = self._extract_methods(class_body)
properties = self._extract_properties(class_body)
fields = self._extract_fields(class_body)
events = self._extract_events(class_body)
if is_monobehaviour or is_scriptable_object:
unity_methods = self._extract_unity_methods(class_body)
coroutines = self._extract_coroutines(class_body)
class_info = ClassInfo(
name=class_name,
line=content[: match.start()].count("\n") + 1,
generics=generics,
bases=bases,
interfaces=interfaces,
visibility=visibility,
modifiers=modifiers,
methods=methods,
properties=properties,
fields=fields,
events=events,
attributes=attributes,
is_monobehaviour=is_monobehaviour,
is_scriptable_object=is_scriptable_object,
unity_methods=unity_methods,
coroutines=coroutines,
)
structure.classes.append(class_info)
# Extract interfaces
interface_pattern = r"(?:^|\n)\s*(?:public\s+)?(?:partial\s+)?interface\s+(\w+)(?:<([^>]+)>)?(?:\s*:\s*([\w\.,\s<>]+))?"
for match in re.finditer(interface_pattern, content):
interface_name = match.group(1)
generics = match.group(2)
extends = match.group(3)
# Extract interface methods
interface_body = self._extract_class_body(content, match.end())
methods = self._extract_interface_methods(interface_body) if interface_body else []
structure.interfaces.append(
{
"name": interface_name,
"line": content[: match.start()].count("\n") + 1,
"generics": generics,
"extends": self._parse_interface_list(extends) if extends else [],
"methods": methods,
}
)
# Extract structs
struct_pattern = (
r"(?:^|\n)\s*(?:public\s+)?(?:readonly\s+)?(?:ref\s+)?struct\s+(\w+)(?:<([^>]+)>)?"
)
for match in re.finditer(struct_pattern, content):
struct_name = match.group(1)
generics = match.group(2)
modifiers = []
if "readonly" in match.group(0):
modifiers.append("readonly")
if "ref" in match.group(0):
modifiers.append("ref")
structure.structs.append(
{
"name": struct_name,
"line": content[: match.start()].count("\n") + 1,
"generics": generics,
"modifiers": modifiers,
}
)
# Extract enums
enum_pattern = r"(?:^|\n)\s*(?:public\s+)?enum\s+(\w+)(?:\s*:\s*(\w+))?"
for match in re.finditer(enum_pattern, content):
enum_name = match.group(1)
base_type = match.group(2)
# Extract enum values
enum_body = self._extract_class_body(content, match.end())
values = self._extract_enum_values(enum_body) if enum_body else []
structure.enums.append(
{
"name": enum_name,
"line": content[: match.start()].count("\n") + 1,
"base_type": base_type,
"values": values,
}
)
# Extract delegates
delegate_pattern = r"(?:^|\n)\s*(?:public\s+)?delegate\s+(\w+)\s+(\w+)\s*\(([^)]*)\)"
for match in re.finditer(delegate_pattern, content):
structure.delegates.append(
{
"return_type": match.group(1),
"name": match.group(2),
"parameters": self._parse_parameters(match.group(3)),
"line": content[: match.start()].count("\n") + 1,
}
)
# Extract global functions (rare in C# but possible)
structure.functions = self._extract_global_functions(content)
# Extract LINQ queries
structure.linq_queries = self._extract_linq_queries(content)
# Count async methods
structure.async_method_count = len(re.findall(r"\basync\s+(?:Task|ValueTask)", content))
# Count lambda expressions
structure.lambda_count = len(re.findall(r"=>\s*(?:\{|[^;{]+;)", content))
# Detect framework
structure.framework = self._detect_framework(content)
# Check for test file
structure.is_test_file = (
"Test" in file_path.name
or file_path.name.endswith("Tests.cs")
or file_path.name.endswith("Test.cs")
or any(part in ["Tests", "Test"] for part in file_path.parts)
)
return structure
calculate_complexity¶
Calculate complexity metrics for C# code.
Calculates: - Cyclomatic complexity - Cognitive complexity - Unity-specific complexity (Coroutines, Update methods) - Async/await complexity - LINQ complexity - Exception handling complexity
| PARAMETER | DESCRIPTION |
|---|---|
content | C# source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
ComplexityMetrics | ComplexityMetrics object with calculated metrics |
Source code in tenets/core/analysis/implementations/csharp_analyzer.py
def calculate_complexity(self, content: str, file_path: Path) -> ComplexityMetrics:
"""Calculate complexity metrics for C# code.
Calculates:
- Cyclomatic complexity
- Cognitive complexity
- Unity-specific complexity (Coroutines, Update methods)
- Async/await complexity
- LINQ complexity
- Exception handling complexity
Args:
content: C# source code
file_path: Path to the file being analyzed
Returns:
ComplexityMetrics object with calculated metrics
"""
metrics = ComplexityMetrics()
# Calculate cyclomatic complexity
complexity = 1
decision_keywords = [
r"\bif\b",
r"\belse\s+if\b",
r"\belse\b",
r"\bfor\b",
r"\bforeach\b",
r"\bwhile\b",
r"\bdo\b",
r"\bswitch\b",
r"\bcase\b",
r"\bcatch\b",
r"\b&&\b",
r"\|\|",
r"\?\s*[^:]+\s*:", # Ternary operator
r"\?\?", # Null coalescing operator
r"\?\.(?!\s*\[)", # Null conditional operator (not including ?.[])
]
for keyword in decision_keywords:
complexity += len(re.findall(keyword, content))
# Add complexity for pattern matching (C# 7+)
# "is" patterns
complexity += len(re.findall(r"\bis\s+\w+\s+\w+", content))
# Switch statements with when filters
complexity += len(re.findall(r"\bswitch\s*\(.*\)\s*\{[\s\S]*?\bwhen\b", content))
# Switch expressions with when clauses (=> and when)
complexity += len(re.findall(r"\bswitch\s*\{[\s\S]*?=>[\s\S]*?\bwhen\b", content))
metrics.cyclomatic = complexity
# Calculate cognitive complexity
cognitive = 0
nesting_level = 0
max_nesting = 0
lines = content.splitlines()
for line in lines:
# Skip comments
if line.strip().startswith("//"):
continue
# Track nesting
opening_braces = line.count("{")
closing_braces = line.count("}")
nesting_level += opening_braces - closing_braces
max_nesting = max(max_nesting, nesting_level)
# Control structures with nesting penalty
control_patterns = [
(r"\bif\b", 1),
(r"\belse\s+if\b", 1),
(r"\belse\b", 0),
(r"\bfor\b", 1),
(r"\bforeach\b", 1),
(r"\bwhile\b", 1),
(r"\bdo\b", 1),
(r"\bswitch\b", 1),
(r"\btry\b", 1),
(r"\bcatch\b", 1),
]
for pattern, weight in control_patterns:
if re.search(pattern, line):
cognitive += weight * (1 + max(0, nesting_level - 1))
metrics.cognitive = cognitive
metrics.max_depth = max_nesting
# Count code elements
metrics.line_count = len(lines)
metrics.code_lines = self._count_code_lines(content)
metrics.comment_lines = self._count_comment_lines(content)
metrics.comment_ratio = (
metrics.comment_lines / metrics.line_count if metrics.line_count > 0 else 0
)
# Count classes, interfaces, etc.
metrics.class_count = len(re.findall(r"\bclass\s+\w+", content))
metrics.interface_count = len(re.findall(r"\binterface\s+\w+", content))
metrics.struct_count = len(re.findall(r"\bstruct\s+\w+", content))
metrics.enum_count = len(re.findall(r"\benum\s+\w+", content))
# Count methods
metrics.method_count = len(
re.findall(
r"(?:public|private|protected|internal)\s+(?:static\s+)?(?:async\s+)?(?:override\s+)?(?:virtual\s+)?(?:[\w<>\[\]]+)\s+\w+\s*\([^)]*\)\s*\{",
content,
)
)
# Property metrics
metrics.property_count = len(
re.findall(
r"(?:public|private|protected|internal)\s+(?:static\s+)?(?:[\w<>\[\]]+)\s+\w+\s*\{\s*(?:get|set)",
content,
)
)
metrics.auto_property_count = len(re.findall(r"\{\s*get;\s*(?:set;)?\s*\}", content))
# Exception handling metrics
metrics.try_blocks = len(re.findall(r"\btry\s*\{", content))
metrics.catch_blocks = len(
re.findall(r"\bcatch(?:\s+when\s*\([^)]*\))?\s*(?:\([^)]*\))?\s*\{", content)
)
metrics.finally_blocks = len(re.findall(r"\bfinally\s*\{", content))
# Count both "throw;" and "throw new ..." forms
metrics.throw_statements = len(re.findall(r"\bthrow\b", content))
# Async/await metrics
metrics.async_methods = len(re.findall(r"\basync\s+(?:Task|ValueTask)", content))
metrics.await_statements = len(re.findall(r"\bawait\s+", content))
# LINQ metrics
metrics.linq_queries = len(re.findall(r"\bfrom\s+\w+\s+in\s+", content))
metrics.linq_methods = len(
re.findall(
r"\.\s*(?:Where|Select|OrderBy|GroupBy|Join|Any|All|First|Last|Single)\s*\(",
content,
)
)
# Unity-specific metrics
if self._is_unity_script(content):
metrics.unity_components = len(
re.findall(r":\s*(?:MonoBehaviour|ScriptableObject)", content)
)
metrics.coroutines = len(re.findall(r"\bIEnumerator\s+\w+\s*\(", content))
metrics.unity_methods = len(
re.findall(
r"\b(?:Start|Update|FixedUpdate|LateUpdate|OnEnable|OnDisable|Awake|OnDestroy|OnCollision(?:Enter|Exit|Stay)?|OnTrigger(?:Enter|Exit|Stay)?)\s*\(",
content,
)
)
metrics.serialize_fields = len(re.findall(r"\[SerializeField\]", content))
metrics.unity_events = len(re.findall(r"\bUnityEvent(?:<[^>]+>)?\s+\w+", content))
# Attribute metrics
metrics.attribute_count = len(re.findall(r"\[[A-Z]\w*(?:\([^)]*\))?\]", content))
# Nullable reference types (C# 8+): properties and locals/params with ? type, plus #nullable enable
nullable_types = len(re.findall(r"[\w<>\[\]]+\?\s+\w+\s*[;=,)\}]", content))
metrics.nullable_refs = nullable_types + len(re.findall(r"#nullable\s+enable", content))
# Calculate maintainability index
import math
if metrics.code_lines > 0:
# Adjusted for C#
async_factor = 1 - (metrics.async_methods * 0.01)
unity_factor = 1 - (getattr(metrics, "coroutines", 0) * 0.02)
mi = (
171
- 5.2 * math.log(max(1, complexity))
- 0.23 * complexity
- 16.2 * math.log(metrics.code_lines)
+ 10 * async_factor
+ 10 * unity_factor
)
metrics.maintainability_index = max(0, min(100, mi))
return metrics
CSSAnalyzer¶
Bases: LanguageAnalyzer
CSS code analyzer with preprocessor and framework support.
Provides comprehensive analysis for CSS files including: - CSS3 features and properties - SCSS/Sass preprocessor features - Less preprocessor features - PostCSS plugins and features - Tailwind CSS utility classes - UnoCSS atomic CSS - CSS-in-JS patterns - CSS Modules - BEM, OOCSS, SMACSS methodologies - Performance metrics - Browser compatibility - Accessibility considerations - Design system patterns
Supports modern CSS development practices and frameworks.
Initialize the CSS analyzer with logger.
Source code in tenets/core/analysis/implementations/css_analyzer.py
def __init__(self):
"""Initialize the CSS analyzer with logger."""
self.logger = get_logger(__name__)
# Tailwind utility patterns
self.tailwind_patterns = self._load_tailwind_patterns()
# UnoCSS patterns
self.unocss_patterns = self._load_unocss_patterns()
# CSS framework patterns
self.framework_patterns = self._load_framework_patterns()
extract_imports¶
Extract import statements from CSS.
Handles: - @import statements - @use (Sass) - @forward (Sass) - url() functions - CSS Modules composes
| PARAMETER | DESCRIPTION |
|---|---|
content | CSS source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[ImportInfo] | List of ImportInfo objects with import details |
Source code in tenets/core/analysis/implementations/css_analyzer.py
def extract_imports(self, content: str, file_path: Path) -> List[ImportInfo]:
"""Extract import statements from CSS.
Handles:
- @import statements
- @use (Sass)
- @forward (Sass)
- url() functions
- CSS Modules composes
Args:
content: CSS source code
file_path: Path to the file being analyzed
Returns:
List of ImportInfo objects with import details
"""
imports = []
# Determine file type
ext = file_path.suffix.lower()
is_scss = ext in [".scss", ".sass"]
is_less = ext == ".less"
# @import statements
import_pattern = r'@import\s+(?:url\()?["\']([^"\']+)["\'](?:\))?(?:\s+([^;]+))?;'
for match in re.finditer(import_pattern, content):
import_path = match.group(1)
media_query = match.group(2)
imports.append(
ImportInfo(
module=import_path,
line=content[: match.start()].count("\n") + 1,
type="import",
is_relative=not import_path.startswith(("http://", "https://", "//")),
media_query=media_query.strip() if media_query else None,
category=self._categorize_css_import(import_path),
)
)
# @use statements (Sass)
if is_scss:
use_pattern = r'@use\s+["\']([^"\']+)["\'](?:\s+as\s+(\w+))?(?:\s+with\s*\(([^)]+)\))?;'
for match in re.finditer(use_pattern, content):
module_path = match.group(1)
namespace = match.group(2)
config = match.group(3)
imports.append(
ImportInfo(
module=module_path,
line=content[: match.start()].count("\n") + 1,
type="use",
is_relative=not module_path.startswith(("http://", "https://", "//")),
namespace=namespace,
config=config,
category=self._categorize_css_import(module_path),
)
)
# @forward statements (Sass)
forward_pattern = r'@forward\s+["\']([^"\']+)["\'](?:\s+(show|hide)\s+([^;]+))?;'
for match in re.finditer(forward_pattern, content):
module_path = match.group(1)
visibility_type = match.group(2)
visibility_items = match.group(3)
# Combine visibility type and items for easier testing
if visibility_type and visibility_items:
visibility = f"{visibility_type} {visibility_items.strip()}"
else:
visibility = None
imports.append(
ImportInfo(
module=module_path,
line=content[: match.start()].count("\n") + 1,
type="forward",
is_relative=not module_path.startswith(("http://", "https://", "//")),
visibility=visibility,
category=self._categorize_css_import(module_path),
)
)
# url() in properties (for fonts, images, etc.)
url_pattern = r'url\(["\']?([^"\')\s]+)["\']?\)'
for match in re.finditer(url_pattern, content):
url_path = match.group(1)
# Skip data URLs and already imported files
if url_path.startswith("data:") or any(imp.module == url_path for imp in imports):
continue
imports.append(
ImportInfo(
module=url_path,
line=content[: match.start()].count("\n") + 1,
type="url",
is_relative=not url_path.startswith(("http://", "https://", "//")),
category=self._categorize_url_import(url_path),
)
)
# CSS Modules composes
composes_pattern = r'composes:\s*([a-zA-Z0-9-_\s]+)\s+from\s+["\']([^"\']+)["\'];'
for match in re.finditer(composes_pattern, content):
classes = match.group(1)
module_path = match.group(2)
imports.append(
ImportInfo(
module=module_path,
line=content[: match.start()].count("\n") + 1,
type="composes",
is_relative=not module_path.startswith(("http://", "https://", "//")),
composes=classes.strip(),
# Alias for tests that expect composed_classes
visibility=None,
category="css_module",
)
)
# Backward compatibility: also attach composed_classes attribute dynamically
for imp in imports:
if imp.type == "composes" and getattr(imp, "composes", None):
# Some tests reference ImportInfo.composed_classes
try:
setattr(imp, "composed_classes", imp.composes)
except Exception:
pass
return imports
extract_exports¶
Extract exported elements from CSS.
In CSS context, exports are: - Classes that can be used by HTML - IDs - Custom properties (CSS variables) - Mixins (SCSS/Less) - Functions (SCSS) - Keyframe animations - Utility classes (Tailwind/UnoCSS)
| PARAMETER | DESCRIPTION |
|---|---|
content | CSS source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[Dict[str, Any]] | List of exported elements |
Source code in tenets/core/analysis/implementations/css_analyzer.py
def extract_exports(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
"""Extract exported elements from CSS.
In CSS context, exports are:
- Classes that can be used by HTML
- IDs
- Custom properties (CSS variables)
- Mixins (SCSS/Less)
- Functions (SCSS)
- Keyframe animations
- Utility classes (Tailwind/UnoCSS)
Args:
content: CSS source code
file_path: Path to the file being analyzed
Returns:
List of exported elements
"""
exports = []
# Parse CSS
ext = file_path.suffix.lower()
is_scss = ext in [".scss", ".sass"]
parser = CSSParser(content, is_scss)
parser.parse()
# Export CSS classes (from selectors only)
classes: Set[str] = set()
for rule in parser.rules:
selector = rule.get("selector", "")
for match in re.finditer(r"\.([a-zA-Z0-9_\\:-]+)", selector):
class_name = match.group(1)
if class_name not in classes:
classes.add(class_name)
pos = content.find("." + class_name)
exports.append(
{
"name": class_name,
"type": "class",
"line": (content[:pos].count("\n") + 1) if pos != -1 else None,
}
)
# Export IDs (from selectors only, avoid hex colors)
ids: Set[str] = set()
for rule in parser.rules:
selector = rule.get("selector", "")
for match in re.finditer(r"#([a-zA-Z0-9_-]+)", selector):
id_name = match.group(1)
if id_name not in ids:
ids.add(id_name)
pos = content.find("#" + id_name)
exports.append(
{
"name": id_name,
"type": "id",
"line": (content[:pos].count("\n") + 1) if pos != -1 else None,
}
)
# Export custom properties
for prop_name, prop_value in parser.custom_properties.items():
exports.append(
{
"name": prop_name,
"type": "custom_property",
"value": prop_value,
}
)
# Export SCSS variables, mixins, functions
if is_scss:
for var_name, var_value in parser.variables.items():
exports.append(
{
"name": var_name,
"type": "scss_variable",
"value": var_value,
}
)
for mixin in parser.mixins:
exports.append(
{
"name": mixin["name"],
"type": "mixin",
"params": mixin["params"],
}
)
for func in parser.functions:
exports.append(
{
"name": func["name"],
"type": "function",
"params": func["params"],
}
)
# Export keyframes
for keyframe in parser.keyframes:
exports.append(
{
"name": keyframe["name"],
"type": "keyframe",
}
)
# Export utility classes (Tailwind/UnoCSS)
if self._is_utility_css(content):
utility_classes = self._extract_utility_classes(content)
for util_class in utility_classes:
exports.append(
{
"name": util_class,
"type": "utility_class",
"framework": self._detect_utility_framework(content),
}
)
return exports
extract_structure¶
Extract CSS document structure.
Extracts: - Rules and selectors - Media queries - CSS architecture patterns - Framework usage - Design tokens - Component structure
| PARAMETER | DESCRIPTION |
|---|---|
content | CSS source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
CodeStructure | CodeStructure object with extracted elements |
Source code in tenets/core/analysis/implementations/css_analyzer.py
def extract_structure(self, content: str, file_path: Path) -> CodeStructure:
"""Extract CSS document structure.
Extracts:
- Rules and selectors
- Media queries
- CSS architecture patterns
- Framework usage
- Design tokens
- Component structure
Args:
content: CSS source code
file_path: Path to the file being analyzed
Returns:
CodeStructure object with extracted elements
"""
structure = CodeStructure()
# Parse CSS
ext = file_path.suffix.lower()
is_scss = ext in [".scss", ".sass"]
is_less = ext == ".less"
parser = CSSParser(content, is_scss)
parser.parse()
# Store parsed data
structure.rules = parser.rules
structure.variables = parser.variables
structure.custom_properties = parser.custom_properties
structure.mixins = parser.mixins
structure.functions = parser.functions
structure.keyframes = parser.keyframes
structure.media_queries = parser.media_queries
structure.supports_rules = parser.supports_rules
structure.max_nesting = parser.max_nesting
# Detect CSS methodology
structure.uses_bem = self._detect_bem(content)
structure.uses_oocss = self._detect_oocss(content)
structure.uses_smacss = self._detect_smacss(content)
structure.uses_atomic = self._detect_atomic_css(content)
# Detect frameworks
structure.is_tailwind = self._detect_tailwind(content, file_path)
structure.is_unocss = self._detect_unocss(content, file_path)
structure.is_bootstrap = self._detect_bootstrap(content)
structure.is_bulma = self._detect_bulma(content)
structure.is_material = self._detect_material(content)
# Count selectors by type (from selectors only)
selectors_joined = ",".join(rule.get("selector", "") for rule in parser.rules)
structure.element_selectors = len(
re.findall(r"(?:(?<=^)|(?<=[\s>+~,(]))[a-zA-Z][a-zA-Z0-9-]*", selectors_joined)
)
structure.class_selectors = len(re.findall(r"\.[a-zA-Z0-9_\\:-]+", selectors_joined))
structure.id_selectors = len(re.findall(r"#[a-zA-Z0-9_-]+", selectors_joined))
structure.attribute_selectors = len(re.findall(r"\[[^\]]+\]", selectors_joined))
structure.pseudo_classes = len(re.findall(r":(?!:)[a-z-]+(?:\([^)]*\))?", selectors_joined))
structure.pseudo_elements = len(re.findall(r"::[a-z-]+", selectors_joined))
# Count CSS3 features
structure.flexbox_usage = len(re.findall(r"display\s*:\s*(?:inline-)?flex", content))
structure.grid_usage = len(re.findall(r"display\s*:\s*grid", content))
structure.custom_property_usage = len(re.findall(r"var\(--[^)]+\)", content))
structure.calc_usage = len(re.findall(r"calc\([^)]+\)", content))
structure.transform_usage = len(re.findall(r"transform\s*:", content))
structure.transition_usage = len(re.findall(r"transition\s*:", content))
structure.animation_usage = len(re.findall(r"animation\s*:", content))
# Count responsive features
structure.media_query_count = len(parser.media_queries)
structure.viewport_units = len(re.findall(r"\d+(?:vw|vh|vmin|vmax)\b", content))
structure.container_queries = len(re.findall(r"@container\s+", content))
# Count modern CSS features
structure.css_nesting = len(
re.findall(r"&\s*[{:.]", content)
) + self._count_nested_selectors(content)
structure.has_layers = bool(re.search(r"@layer\s+", content))
structure.has_cascade_layers = len(re.findall(r"@layer\s+[a-z-]+\s*[{,]", content))
# Design system detection
structure.has_design_tokens = self._detect_design_tokens(content)
structure.color_variables = self._count_color_variables(parser.custom_properties)
structure.spacing_variables = self._count_spacing_variables(parser.custom_properties)
structure.typography_variables = self._count_typography_variables(parser.custom_properties)
# Component-based structure
structure.component_count = self._count_components(content)
structure.utility_count = self._count_utilities(content)
# PostCSS features
structure.uses_postcss = self._detect_postcss(content, file_path)
structure.postcss_plugins = self._detect_postcss_plugins(content)
# CSS-in-JS patterns
structure.is_css_modules = self._detect_css_modules(content, file_path)
structure.is_styled_components = self._detect_styled_components(content)
# Performance indicators
structure.unused_variables = self._find_unused_variables(content, parser)
structure.duplicate_properties = self._find_duplicate_properties(parser.rules)
structure.vendor_prefixes = len(re.findall(r"-(?:webkit|moz|ms|o)-", content))
# Accessibility
structure.focus_styles = len(re.findall(r":focus\s*[{,]", content))
structure.focus_visible = len(re.findall(r":focus-visible\s*[{,]", content))
structure.reduced_motion = len(re.findall(r"prefers-reduced-motion", content))
structure.high_contrast = len(re.findall(r"prefers-contrast", content))
structure.color_scheme = len(re.findall(r"prefers-color-scheme", content))
return structure
calculate_complexity¶
Calculate complexity metrics for CSS.
Calculates: - Selector complexity - Specificity metrics - Rule complexity - Nesting depth - Framework complexity - Performance score - Maintainability index
| PARAMETER | DESCRIPTION |
|---|---|
content | CSS source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
ComplexityMetrics | ComplexityMetrics object with calculated metrics |
Source code in tenets/core/analysis/implementations/css_analyzer.py
def calculate_complexity(self, content: str, file_path: Path) -> ComplexityMetrics:
"""Calculate complexity metrics for CSS.
Calculates:
- Selector complexity
- Specificity metrics
- Rule complexity
- Nesting depth
- Framework complexity
- Performance score
- Maintainability index
Args:
content: CSS source code
file_path: Path to the file being analyzed
Returns:
ComplexityMetrics object with calculated metrics
"""
metrics = ComplexityMetrics()
# Parse CSS
ext = file_path.suffix.lower()
is_scss = ext in [".scss", ".sass"]
parser = CSSParser(content, is_scss)
parser.parse()
# Basic metrics
lines = content.split("\n")
metrics.line_count = len(lines)
metrics.code_lines = len([l for l in lines if l.strip() and not l.strip().startswith("//")])
metrics.comment_lines = len(re.findall(r"/\*.*?\*/", content, re.DOTALL))
if is_scss:
metrics.comment_lines += len([l for l in lines if l.strip().startswith("//")])
# Rule metrics
metrics.total_rules = len(parser.rules)
metrics.total_selectors = sum(len(rule["selector"].split(",")) for rule in parser.rules)
# Calculate average specificity
total_specificity = [0, 0, 0]
max_specificity = [0, 0, 0]
for rule in parser.rules:
spec = rule["specificity"]
total_specificity[0] += spec[0]
total_specificity[1] += spec[1]
total_specificity[2] += spec[2]
if spec[0] > max_specificity[0]:
max_specificity = spec
elif spec[0] == max_specificity[0] and spec[1] > max_specificity[1]:
max_specificity = spec
elif (
spec[0] == max_specificity[0]
and spec[1] == max_specificity[1]
and spec[2] > max_specificity[2]
):
max_specificity = spec
if metrics.total_rules > 0:
metrics.avg_specificity = [
total_specificity[0] / metrics.total_rules,
total_specificity[1] / metrics.total_rules,
total_specificity[2] / metrics.total_rules,
]
else:
metrics.avg_specificity = [0, 0, 0]
metrics.max_specificity = max_specificity
# Selector complexity
metrics.complex_selectors = 0
metrics.overqualified_selectors = 0
for rule in parser.rules:
selector = rule["selector"]
# Complex selector (too many parts)
if len(selector.split()) > 3:
metrics.complex_selectors += 1
# Overqualified (element with class/id)
if re.search(r"[a-z]+\.[a-z-]+|[a-z]+#[a-z-]+", selector, re.IGNORECASE):
metrics.overqualified_selectors += 1
# Important usage
metrics.important_count = len(re.findall(r"!important", content))
# Media query complexity
metrics.media_query_count = len(parser.media_queries)
metrics.media_query_complexity = sum(
len(mq["condition"].split("and")) for mq in parser.media_queries
)
# Nesting depth (for SCSS)
metrics.max_nesting_depth = parser.max_nesting
# Color usage
metrics.unique_colors = len(
set(
re.findall(
r"#[0-9a-fA-F]{3,8}|rgb\([^)]+\)|rgba\([^)]+\)|hsl\([^)]+\)|hsla\([^)]+\)",
content,
)
)
)
# Font usage
metrics.unique_fonts = len(set(re.findall(r"font-family\s*:\s*([^;]+);", content)))
# Z-index usage
z_indices = re.findall(r"z-index\s*:\s*(-?\d+)", content)
metrics.z_index_count = len(z_indices)
if z_indices:
metrics.max_z_index = max(int(z) for z in z_indices)
else:
metrics.max_z_index = 0
# File size metrics
metrics.file_size = len(content.encode("utf-8"))
metrics.gzip_ratio = self._estimate_gzip_ratio(content)
# Framework-specific metrics
if self._detect_tailwind(content, file_path):
metrics.tailwind_classes = self._count_tailwind_classes(content)
metrics.custom_utilities = self._count_custom_utilities(content)
if self._detect_unocss(content, file_path):
metrics.unocss_classes = self._count_unocss_classes(content)
# Calculate CSS complexity score
complexity_score = (
metrics.total_rules * 0.1
+ metrics.complex_selectors * 2
+ metrics.overqualified_selectors * 1.5
+ metrics.important_count * 3
+ metrics.max_nesting_depth * 1
+ (metrics.max_specificity[0] * 10) # IDs weighted heavily
+ (metrics.max_specificity[1] * 2) # Classes
+ (metrics.max_specificity[2] * 0.5) # Elements
)
metrics.complexity_score = complexity_score
# Performance score
performance_score = 100
# Deduct for complexity
performance_score -= min(30, complexity_score / 10)
# Deduct for !important
performance_score -= min(20, metrics.important_count * 2)
# Deduct for deep nesting
performance_score -= min(10, metrics.max_nesting_depth * 2)
# Deduct for excessive specificity
performance_score -= min(10, metrics.max_specificity[0] * 5)
# Bonus for CSS variables usage
if len(parser.custom_properties) > 0:
performance_score += min(10, len(parser.custom_properties) * 0.5)
metrics.performance_score = max(0, performance_score)
# Calculate maintainability index
import math
if metrics.code_lines > 0:
# Factors affecting CSS maintainability
specificity_factor = 1 - (sum(metrics.avg_specificity) * 0.1)
important_factor = 1 - (metrics.important_count * 0.02)
nesting_factor = 1 - (metrics.max_nesting_depth * 0.05)
organization_factor = 1 if len(parser.custom_properties) > 0 else 0.8
mi = (
171
- 5.2 * math.log(max(1, metrics.total_rules))
- 0.23 * complexity_score
- 16.2 * math.log(max(1, metrics.code_lines))
+ 20 * specificity_factor
+ 10 * important_factor
+ 10 * nesting_factor
+ 10 * organization_factor
)
metrics.maintainability_index = max(0, min(100, mi))
else:
metrics.maintainability_index = 100
return metrics
DartAnalyzer¶
Bases: LanguageAnalyzer
Dart code analyzer with Flutter support.
Provides comprehensive analysis for Dart files including: - Import and export directives - Part and library declarations - Classes with mixins and extensions - Null safety features (?, !, late) - Async/await, Future, and Stream handling - Flutter widgets and lifecycle methods - Factory and named constructors - Extension methods - Annotations and metadata - Generics and type parameters
Supports Dart 2.x with null safety and Flutter framework patterns.
Initialize the Dart analyzer with logger.
Source code in tenets/core/analysis/implementations/dart_analyzer.py
extract_imports¶
Extract import, export, part, and library directives from Dart code.
Handles: - import statements: import 'package:flutter/material.dart'; - export statements: export 'src/widget.dart'; - part statements: part 'implementation.dart'; - part of statements: part of 'library.dart'; - library declarations: library my_library; - Conditional imports: import 'stub.dart' if (dart.library.io) 'io.dart'; - Show/hide clauses: import 'dart:math' show Random hide PI; - Deferred imports: import 'big_lib.dart' deferred as big;
| PARAMETER | DESCRIPTION |
|---|---|
content | Dart source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[ImportInfo] | List of ImportInfo objects with import details |
Source code in tenets/core/analysis/implementations/dart_analyzer.py
def extract_imports(self, content: str, file_path: Path) -> List[ImportInfo]:
"""Extract import, export, part, and library directives from Dart code.
Handles:
- import statements: import 'package:flutter/material.dart';
- export statements: export 'src/widget.dart';
- part statements: part 'implementation.dart';
- part of statements: part of 'library.dart';
- library declarations: library my_library;
- Conditional imports: import 'stub.dart' if (dart.library.io) 'io.dart';
- Show/hide clauses: import 'dart:math' show Random hide PI;
- Deferred imports: import 'big_lib.dart' deferred as big;
Args:
content: Dart source code
file_path: Path to the file being analyzed
Returns:
List of ImportInfo objects with import details
"""
imports = []
lines = content.split("\n")
for i, line in enumerate(lines, 1):
# Skip comments
if line.strip().startswith("//"):
continue
# Import statements - handle more complex patterns with show/hide
# First, try to extract the basic import and parse show/hide separately
basic_import_pattern = r"^\s*import\s+['\"]([^'\"]+)['\"](?:\s+if\s*\([^)]+\)\s*['\"][^'\"]+['\"]*)?(?:\s+deferred)?(?:\s+as\s+(\w+))?(.*?);"
match = re.match(basic_import_pattern, line)
if match:
module_path = match.group(1)
alias = match.group(2)
show_hide_part = match.group(3) if match.group(3) else ""
# Parse show/hide clauses
show_symbols = []
hide_symbols = []
is_deferred = "deferred" in line
# Extract show clause
show_match = re.search(r"\bshow\s+([^;]+?)(?:\s+hide|$)", show_hide_part + " ")
if show_match:
show_symbols = self._parse_symbols(show_match.group(1))
# Extract hide clause
hide_match = re.search(r"\bhide\s+([^;]+?)(?:\s+show|$)", show_hide_part + " ")
if hide_match:
hide_symbols = self._parse_symbols(hide_match.group(1))
# Determine import type
import_type = "import"
is_package = module_path.startswith("package:")
is_dart_core = module_path.startswith("dart:")
is_relative = module_path.startswith("../") or module_path.startswith("./")
# Categorize the import
category = self._categorize_import(module_path)
imports.append(
ImportInfo(
module=module_path,
alias=alias,
line=i,
type=import_type,
is_relative=is_relative,
is_package=is_package,
is_dart_core=is_dart_core,
is_deferred=is_deferred,
category=category,
show_symbols=show_symbols if show_symbols else [],
hide_symbols=hide_symbols if hide_symbols else [],
)
)
# Export statements
export_pattern = r"""
^\s*export\s+
['"]([^'"]+)['"]\s*
(?:show\s+([^;]+))?\s*
(?:hide\s+([^;]+))?\s*
;
"""
match = re.match(export_pattern, line, re.VERBOSE)
if match:
module_path = match.group(1)
show_clause = match.group(2)
hide_clause = match.group(3)
imports.append(
ImportInfo(
module=module_path,
line=i,
type="export",
is_relative=not module_path.startswith("package:")
and not module_path.startswith("dart:"),
show_symbols=self._parse_symbols(show_clause) if show_clause else [],
hide_symbols=self._parse_symbols(hide_clause) if hide_clause else [],
category=self._categorize_import(module_path),
)
)
# Part statements
part_pattern = r"^\s*part\s+['\"]([^'\"]+)['\"]\s*;"
match = re.match(part_pattern, line)
if match:
imports.append(
ImportInfo(
module=match.group(1),
line=i,
type="part",
is_relative=True,
is_part_file=True,
)
)
# Part of statements
part_of_pattern = r"^\s*part\s+of\s+['\"]?([^'\";\s]+)['\"]?\s*;"
match = re.match(part_of_pattern, line)
if match:
imports.append(
ImportInfo(
module=match.group(1),
line=i,
type="part_of",
is_relative=False,
is_library_part=True,
)
)
# Library declaration
library_pattern = r"^\s*library\s+(\w+(?:\.\w+)*)\s*;"
match = re.match(library_pattern, line)
if match:
imports.append(
ImportInfo(
module=match.group(1),
line=i,
type="library",
is_relative=False,
is_library_declaration=True,
)
)
# Handle conditional, multi-line imports like:
# import 'stub.dart'
# if (dart.library.io) 'io_implementation.dart'
# if (dart.library.html) 'web_implementation.dart';
cond_import_pattern = (
r"import\s+['\"]([^'\"]+)['\"]\s*(?:\s*if\s*\([^)]+\)\s*['\"][^'\"]+['\"]\s*)+;"
)
for m in re.finditer(cond_import_pattern, content, re.MULTILINE):
first_module = m.group(1)
# Avoid duplicates if already added (e.g., if written in one line)
if not any(imp.module == first_module and imp.type == "import" for imp in imports):
imports.append(
ImportInfo(
module=first_module,
line=content[: m.start()].count("\n") + 1,
type="import",
is_relative=first_module.startswith("../") or first_module.startswith("./"),
is_package=first_module.startswith("package:"),
is_dart_core=first_module.startswith("dart:"),
category=self._categorize_import(first_module),
conditional=True,
)
)
return imports
extract_exports¶
Extract exported symbols from Dart code.
In Dart, exports include: - Public classes (not prefixed with _) - Public functions - Public variables and constants - Public typedefs - Public enums - Extension methods
| PARAMETER | DESCRIPTION |
|---|---|
content | Dart source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[Dict[str, Any]] | List of exported symbols |
Source code in tenets/core/analysis/implementations/dart_analyzer.py
def extract_exports(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
"""Extract exported symbols from Dart code.
In Dart, exports include:
- Public classes (not prefixed with _)
- Public functions
- Public variables and constants
- Public typedefs
- Public enums
- Extension methods
Args:
content: Dart source code
file_path: Path to the file being analyzed
Returns:
List of exported symbols
"""
exports = []
# Public classes (including abstract and mixins)
class_pattern = r"^\s*(?:abstract\s+)?(?:final\s+)?(?:base\s+)?(?:interface\s+)?(?:mixin\s+)?class\s+([A-Z]\w*)"
for match in re.finditer(class_pattern, content, re.MULTILINE):
class_name = match.group(1)
modifiers = []
match_str = match.group(0)
if "abstract" in match_str:
modifiers.append("abstract")
if "final" in match_str:
modifiers.append("final")
if "base" in match_str:
modifiers.append("base")
if "interface" in match_str:
modifiers.append("interface")
if "mixin" in match_str:
modifiers.append("mixin")
exports.append(
{
"name": class_name,
"type": "class",
"line": content[: match.start()].count("\n") + 1,
"modifiers": modifiers,
"is_public": True,
}
)
# Mixins
mixin_pattern = r"^\s*(?:base\s+)?mixin\s+([A-Z]\w*)"
for match in re.finditer(mixin_pattern, content, re.MULTILINE):
if not any(e["name"] == match.group(1) for e in exports): # Avoid duplicates
exports.append(
{
"name": match.group(1),
"type": "mixin",
"line": content[: match.start()].count("\n") + 1,
"is_public": True,
}
)
# Public functions (not starting with _), including async*, sync*
func_pattern = r"^\s*(?:Future<?[^>]*>?\s+|Stream<?[^>]*>?\s+|void\s+|[\w<>]+\s+)?([a-z]\w*)\s*(?:<[^>]+>)?\s*\([^\{]*\)\s*(?:(?:async|sync)\s*\*|async)?\s*(?:=>|\{)"
for match in re.finditer(func_pattern, content, re.MULTILINE):
func_name = match.group(1)
if not func_name.startswith("_"):
snippet = match.group(0)
exports.append(
{
"name": func_name,
"type": "function",
"line": content[: match.start()].count("\n") + 1,
"is_public": True,
"is_async": ("async" in snippet),
}
)
# Public variables and constants
var_pattern = r"^\s*(?:final\s+|const\s+|late\s+)?(?:static\s+)?(?:final\s+|const\s+)?(?:[\w<>?]+\s+)?([a-z]\w*)\s*(?:=|;)"
for match in re.finditer(var_pattern, content, re.MULTILINE):
var_name = match.group(1)
if not var_name.startswith("_") and var_name not in [
"if",
"for",
"while",
"return",
"class",
"import",
]:
var_type = "constant" if "const" in match.group(0) else "variable"
exports.append(
{
"name": var_name,
"type": var_type,
"line": content[: match.start()].count("\n") + 1,
"is_public": True,
"is_final": "final" in match.group(0),
"is_late": "late" in match.group(0),
}
)
# Enums
enum_pattern = r"^\s*enum\s+([A-Z]\w*)"
for match in re.finditer(enum_pattern, content, re.MULTILINE):
exports.append(
{
"name": match.group(1),
"type": "enum",
"line": content[: match.start()].count("\n") + 1,
"is_public": True,
}
)
# Typedefs
typedef_pattern = r"^\s*typedef\s+([A-Z]\w*)"
for match in re.finditer(typedef_pattern, content, re.MULTILINE):
exports.append(
{
"name": match.group(1),
"type": "typedef",
"line": content[: match.start()].count("\n") + 1,
"is_public": True,
}
)
# Extension methods
extension_pattern = r"^\s*extension\s+(?:([A-Z]\w*)\s+)?on\s+([A-Z]\w*)"
for match in re.finditer(extension_pattern, content, re.MULTILINE):
extension_name = match.group(1) or f"Extension on {match.group(2)}"
exports.append(
{
"name": extension_name,
"type": "extension",
"line": content[: match.start()].count("\n") + 1,
"on_type": match.group(2),
"is_public": True,
}
)
return exports
extract_structure¶
Extract code structure from Dart file.
Extracts: - Classes with inheritance, mixins, and interfaces - Constructors (default, named, factory) - Methods and getters/setters - Flutter widgets and lifecycle methods - Async functions and streams - Extension methods - Null safety features - Annotations
| PARAMETER | DESCRIPTION |
|---|---|
content | Dart source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
CodeStructure | CodeStructure object with extracted elements |
Source code in tenets/core/analysis/implementations/dart_analyzer.py
def extract_structure(self, content: str, file_path: Path) -> CodeStructure:
"""Extract code structure from Dart file.
Extracts:
- Classes with inheritance, mixins, and interfaces
- Constructors (default, named, factory)
- Methods and getters/setters
- Flutter widgets and lifecycle methods
- Async functions and streams
- Extension methods
- Null safety features
- Annotations
Args:
content: Dart source code
file_path: Path to the file being analyzed
Returns:
CodeStructure object with extracted elements
"""
structure = CodeStructure()
# Detect if it's a Flutter file
structure.is_flutter = self._is_flutter_file(content)
# Extract classes
class_pattern = r"""
^\s*(?:@\w+(?:\([^)]*\))?\s*)* # Annotations
(?:(abstract)\s+)?
(?:(final)\s+)?
(?:(base)\s+)?
(?:(interface)\s+)?
(?:(mixin)\s+)?
(?:(sealed)\s+)?
class\s+(\w+)
(?:<([^>\n{}]*?)>+)? # Generics (tolerant of nested '>')
(?:\s+extends\s+([^\{]+?))?
(?:\s+with\s+([^\{]+?))?
(?:\s+implements\s+([^\{]+?))?
\s*\{
"""
for match in re.finditer(class_pattern, content, re.VERBOSE | re.MULTILINE):
class_name = match.group(7)
# Extract class modifiers
modifiers = []
if match.group(1):
modifiers.append("abstract")
if match.group(2):
modifiers.append("final")
if match.group(3):
modifiers.append("base")
if match.group(4):
modifiers.append("interface")
if match.group(5):
modifiers.append("mixin")
if match.group(6):
modifiers.append("sealed")
# Parse inheritance
extends = match.group(9).strip() if match.group(9) else None
mixins = self._parse_type_list(match.group(10)) if match.group(10) else []
implements = self._parse_type_list(match.group(11)) if match.group(11) else []
# Check if it's a Flutter widget
is_widget = False
widget_type = None
if extends:
# Prefer concrete State<T> first to avoid misclassification
if re.search(r"\bState<", extends):
is_widget = True
widget_type = "state"
elif "StatelessWidget" in extends:
is_widget = True
widget_type = "stateless"
elif "StatefulWidget" in extends:
is_widget = True
widget_type = "stateful"
elif "InheritedWidget" in extends:
is_widget = True
widget_type = "inherited"
# Extract class body
class_body = self._extract_class_body(content, match.end())
if class_body:
# Extract constructors
constructors = self._extract_constructors(class_body, class_name)
# Extract methods
methods = self._extract_methods(class_body)
# Extract fields
fields = self._extract_fields(class_body)
# Extract getters/setters
properties = self._extract_properties(class_body)
else:
constructors = []
methods = []
fields = []
properties = []
class_info = ClassInfo(
name=class_name,
line=content[: match.start()].count("\n") + 1,
modifiers=modifiers,
generics=match.group(8),
bases=[extends] if extends else [],
mixins=mixins,
interfaces=implements,
constructors=constructors,
methods=methods,
fields=fields,
properties=properties,
is_widget=is_widget,
widget_type=widget_type,
is_sealed="sealed" in modifiers,
)
# Balance generics angle brackets if regex captured incomplete nested generics
if class_info.generics:
try:
opens = class_info.generics.count("<")
closes = class_info.generics.count(">")
if opens > closes:
class_info.generics = class_info.generics + (">" * (opens - closes))
except Exception:
pass
structure.classes.append(class_info)
# Fallback: capture classes with complex generic bounds that the primary regex may miss
try:
existing = {c.name for c in structure.classes}
complex_class_pattern = r"""^\s*
(?:(abstract|final|base|interface|mixin|sealed)\s+)*
class\s+(\w+)\s*<([^\n{]+)>\s*
(?:extends\s+([^\n{]+?))?\s*
(?:with\s+([^\n{]+?))?\s*
(?:implements\s+([^\n{]+?))?\s*\{
"""
for m in re.finditer(complex_class_pattern, content, re.MULTILINE | re.VERBOSE):
name = m.group(2)
if name in existing:
continue
modifiers_raw = m.group(1) or ""
modifiers = [mod for mod in modifiers_raw.split() if mod]
generics = m.group(3).strip()
extends = m.group(4).strip() if m.group(4) else None
mixins = self._parse_type_list(m.group(5)) if m.group(5) else []
implements = self._parse_type_list(m.group(6)) if m.group(6) else []
structure.classes.append(
ClassInfo(
name=name,
line=content[: m.start()].count("\n") + 1,
generics=generics,
bases=[extends] if extends else [],
mixins=mixins,
interfaces=implements,
constructors=[],
methods=[],
fields=[],
properties=[],
modifiers=modifiers,
)
)
except Exception:
pass
# Extract mixins (standalone)
mixin_pattern = r"^\s*(?:base\s+)?mixin\s+(\w+)(?:<([^>]+)>)?(?:\s+on\s+([^{]+))?\s*\{"
for match in re.finditer(mixin_pattern, content, re.MULTILINE):
mixin_name = match.group(1)
# Avoid duplicates with mixin classes
if not any(c.name == mixin_name for c in structure.classes):
structure.mixins.append(
{
"name": mixin_name,
"line": content[: match.start()].count("\n") + 1,
"generics": match.group(2),
"on_types": self._parse_type_list(match.group(3)) if match.group(3) else [],
}
)
# Extract top-level functions
func_pattern = r"""
^\s*(?:@\w+(?:\([^)]*\))?\s*)* # Annotations
(?:(Future|Stream)(?:<[^>]+>)?\s+)?
(?:(void|[\w<>?]+|\([^)]+\))\s+)? # Return type or record type
([a-zA-Z_]\w*)\s*
(?:<[^>]+>)?\s* # Generic parameters
\(([^)]*)\)\s*
(?:(?:async|sync)\s*\*|async)?\s* # async, async*, or sync*
(?:=>|\{)
"""
for match in re.finditer(func_pattern, content, re.VERBOSE | re.MULTILINE):
func_name = match.group(3)
# Skip if it's inside a class
if not self._is_top_level(content, match.start()):
continue
return_type = match.group(1) or match.group(2)
params = match.group(4)
span = content[match.start() : match.end()]
is_async = "async" in span
is_generator = "*" in span
func_info = FunctionInfo(
name=func_name,
line=content[: match.start()].count("\n") + 1,
return_type=return_type,
parameters=self._parse_parameters(params),
is_async=is_async,
is_generator=is_generator,
is_private=func_name.startswith("_"),
)
structure.functions.append(func_info)
# Extract enums (brace-aware, supports enhanced enums with methods)
enum_head_pattern = r"^\s*enum\s+(\w+)(?:\s*<[^>]+>)?(?:\s+implements\s+[^\{]+)?\s*\{"
for m in re.finditer(enum_head_pattern, content, re.MULTILINE):
enum_name = m.group(1)
enum_body = self._extract_block(content, m.end()) or ""
if enum_body is None:
continue
# Determine the values section: up to first top-level ';' if present
values_part = enum_body
depth = 0
cutoff = None
for i, ch in enumerate(enum_body):
if ch == "{":
depth += 1
elif ch == "}":
depth = max(0, depth - 1)
elif ch == ";" and depth == 0:
cutoff = i
break
if cutoff is not None:
values_part = enum_body[:cutoff]
values = self._parse_enum_values(values_part)
structure.enums.append(
{
"name": enum_name,
"line": content[: m.start()].count("\n") + 1,
"values": values,
"has_enhanced_features": ("(" in values_part) or (cutoff is not None),
}
)
# Extract extensions
extension_pattern = r"^\s*extension\s+(?:(\w+)\s+)?on\s+([^\{]+)\s*\{"
for match in re.finditer(extension_pattern, content, re.MULTILINE):
extension_name = match.group(1) or f"on {match.group(2)}"
on_type = match.group(2).strip()
structure.extensions.append(
{
"name": extension_name,
"line": content[: match.start()].count("\n") + 1,
"on_type": on_type,
}
)
# Extract typedefs
typedef_pattern = r"^\s*typedef\s+(\w+)(?:<[^>]+>)?\s*=\s*([^;]+);"
for match in re.finditer(typedef_pattern, content, re.MULTILINE):
structure.typedefs.append(
{
"name": match.group(1),
"line": content[: match.start()].count("\n") + 1,
"definition": match.group(2).strip(),
}
)
# Count null safety features
structure.nullable_types = len(re.findall(r"\w+\?(?:\s|,|\))", content))
structure.null_assertions = len(re.findall(r"\w+!(?:\.|;|\s|\))", content))
structure.late_variables = len(re.findall(r"\blate\s+", content))
structure.null_aware_operators = len(re.findall(r"\?\?|\?\.", content))
# Count async features
structure.async_functions = len(re.findall(r"\basync\s*(?:\*)?\s*(?:=>|\{)", content))
structure.await_expressions = len(re.findall(r"\bawait\s+", content))
structure.future_count = len(re.findall(r"\bFuture(?:\s*<|[.(])", content))
structure.stream_count = len(re.findall(r"\bStream(?:\s*<|[.(])", content))
# Detect test file
structure.is_test_file = (
"_test.dart" in file_path.name or file_path.parts and "test" in file_path.parts
)
# Detect main function
structure.has_main = bool(re.search(r"\bvoid\s+main\s*\(", content))
return structure
calculate_complexity¶
Calculate complexity metrics for Dart code.
Calculates: - Cyclomatic complexity - Cognitive complexity - Null safety complexity - Async complexity - Flutter-specific complexity - Class hierarchy depth
| PARAMETER | DESCRIPTION |
|---|---|
content | Dart source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
ComplexityMetrics | ComplexityMetrics object with calculated metrics |
Source code in tenets/core/analysis/implementations/dart_analyzer.py
def calculate_complexity(self, content: str, file_path: Path) -> ComplexityMetrics:
"""Calculate complexity metrics for Dart code.
Calculates:
- Cyclomatic complexity
- Cognitive complexity
- Null safety complexity
- Async complexity
- Flutter-specific complexity
- Class hierarchy depth
Args:
content: Dart source code
file_path: Path to the file being analyzed
Returns:
ComplexityMetrics object with calculated metrics
"""
metrics = ComplexityMetrics()
# Calculate cyclomatic complexity
complexity = 1
decision_keywords = [
r"\bif\b",
r"\belse\s+if\b",
r"\belse\b",
r"\bfor\b",
r"\bwhile\b",
r"\bdo\b",
r"\bswitch\b",
r"\bcase\b",
r"\bcatch\b",
r"\b\?\s*[^:]+\s*:", # Ternary operator
r"\?\?", # Null coalescing
r"&&",
r"\|\|",
]
for keyword in decision_keywords:
complexity += len(re.findall(keyword, content))
metrics.cyclomatic = complexity
# Calculate cognitive complexity
cognitive = 0
nesting_level = 0
max_nesting = 0
lines = content.split("\n")
for line in lines:
# Skip comments
if line.strip().startswith("//"):
continue
# Track nesting
opening_braces = line.count("{")
closing_braces = line.count("}")
nesting_level += opening_braces - closing_braces
max_nesting = max(max_nesting, nesting_level)
# Control structures with nesting penalty
control_patterns = [
(r"\bif\b", 1),
(r"\belse\s+if\b", 1),
(r"\belse\b", 0),
(r"\bfor\b", 1),
(r"\bwhile\b", 1),
(r"\bdo\b", 1),
(r"\bswitch\b", 1),
(r"\btry\b", 1),
(r"\bcatch\b", 1),
]
for pattern, weight in control_patterns:
if re.search(pattern, line):
cognitive += weight * (1 + max(0, nesting_level - 1))
metrics.cognitive = cognitive
metrics.max_depth = max_nesting
# Count code elements
metrics.line_count = len(lines)
metrics.code_lines = len([l for l in lines if l.strip() and not l.strip().startswith("//")])
metrics.comment_lines = len([l for l in lines if l.strip().startswith("//")])
metrics.comment_ratio = (
metrics.comment_lines / metrics.line_count if metrics.line_count > 0 else 0
)
# Count classes and methods
metrics.class_count = len(re.findall(r"\bclass\s+\w+", content))
metrics.mixin_count = len(re.findall(r"\bmixin\s+\w+", content))
metrics.method_count = len(
re.findall(
r"(?:^|\s)(?:Future|Stream|void|[\w<>]+)\s+\w+\s*\([^)]*\)\s*(?:async\s*)?(?:=>|\{)",
content,
)
)
# Null safety metrics
metrics.nullable_types = len(re.findall(r"\w+\?(?:\s|,|\))", content))
metrics.null_assertions = len(re.findall(r"\w+!(?:\.|;|\s|\))", content))
metrics.late_keywords = len(re.findall(r"\blate\s+", content))
metrics.null_aware_ops = len(re.findall(r"\?\?|\?\.|\?\.\?", content))
metrics.required_keywords = len(re.findall(r"\brequired\s+", content))
# Async metrics
metrics.async_functions = len(re.findall(r"\basync\s*(?:\*)?\s*(?:=>|\{)", content))
metrics.await_count = len(re.findall(r"\bawait\s+", content))
metrics.future_count = len(re.findall(r"\bFuture(?:\s*<|[.(])", content))
metrics.stream_count = len(re.findall(r"\bStream(?:\s*<|[.(])", content))
metrics.completer_count = len(re.findall(r"\bCompleter<", content))
# Flutter-specific metrics
if self._is_flutter_file(content):
metrics.widget_count = len(re.findall(r"\bWidget\b", content))
metrics.build_methods = len(re.findall(r"\bWidget\s+build\s*\(", content))
metrics.setstate_calls = len(re.findall(r"\bsetState\s*\(", content))
metrics.stateful_widgets = len(re.findall(r"extends\s+StatefulWidget", content))
metrics.stateless_widgets = len(re.findall(r"extends\s+StatelessWidget", content))
metrics.inherited_widgets = len(re.findall(r"extends\s+InheritedWidget", content))
# Flutter hooks and keys
metrics.keys_used = len(
re.findall(r"\bKey\s*\(|GlobalKey|ValueKey|ObjectKey|UniqueKey", content)
)
metrics.context_usage = len(re.findall(r"\bBuildContext\b", content))
# Exception handling metrics
metrics.try_blocks = len(re.findall(r"\btry\s*\{", content))
metrics.catch_blocks = len(re.findall(r"\bcatch\s*\(", content))
metrics.finally_blocks = len(re.findall(r"\bfinally\s*\{", content))
metrics.throw_statements = len(re.findall(r"\bthrow\s+", content))
metrics.rethrow_statements = len(re.findall(r"\brethrow\s*;", content))
# Type system metrics
metrics.generic_types = len(re.findall(r"<[\w\s,<>]+>", content))
metrics.type_parameters = len(re.findall(r"<\w+(?:\s+extends\s+\w+)?>", content))
metrics.dynamic_types = len(re.findall(r"\bdynamic\b", content))
metrics.var_declarations = len(re.findall(r"\bvar\s+\w+", content))
# Calculate maintainability index
import math
if metrics.code_lines > 0:
# Adjusted for Dart
null_safety_factor = 1 - (metrics.null_assertions * 0.01)
async_factor = 1 - (metrics.async_functions * 0.01)
flutter_factor = (
1 - (metrics.setstate_calls * 0.02) if hasattr(metrics, "setstate_calls") else 1
)
type_factor = 1 + ((metrics.nullable_types - metrics.dynamic_types) * 0.001)
mi = (
171
- 5.2 * math.log(max(1, complexity))
- 0.23 * complexity
- 16.2 * math.log(metrics.code_lines)
+ 10 * null_safety_factor
+ 5 * async_factor
+ 5 * flutter_factor
+ 5 * type_factor
)
metrics.maintainability_index = max(0, min(100, mi))
return metrics
GDScriptAnalyzer¶
Bases: LanguageAnalyzer
GDScript code analyzer for Godot development.
Provides comprehensive analysis for GDScript files including: - Preload and load statements - Class inheritance (extends) - Signal declarations and connections - Export variable declarations - Onready variables and node references - Godot lifecycle methods (_ready, _process, etc.) - Tool scripts and custom resources - Typed GDScript (static typing) - Inner classes - Setget properties - Remote and master/puppet keywords (networking)
Supports Godot 3.x and 4.x GDScript syntax.
Initialize the GDScript analyzer with logger.
Source code in tenets/core/analysis/implementations/gdscript_analyzer.py
extract_imports¶
Extract preload, load, and class references from GDScript code.
Handles: - preload statements: preload("res://path/to/script.gd") - load statements: load("res://path/to/resource.tres") - const preloads: const MyClass = preload("res://MyClass.gd") - class_name declarations (Godot 3.1+) - Tool script declarations
| PARAMETER | DESCRIPTION |
|---|---|
content | GDScript source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[ImportInfo] | List of ImportInfo objects with import details |
Source code in tenets/core/analysis/implementations/gdscript_analyzer.py
def extract_imports(self, content: str, file_path: Path) -> List[ImportInfo]:
"""Extract preload, load, and class references from GDScript code.
Handles:
- preload statements: preload("res://path/to/script.gd")
- load statements: load("res://path/to/resource.tres")
- const preloads: const MyClass = preload("res://MyClass.gd")
- class_name declarations (Godot 3.1+)
- Tool script declarations
Args:
content: GDScript source code
file_path: Path to the file being analyzed
Returns:
List of ImportInfo objects with import details
"""
imports = []
lines = content.split("\n")
for i, line in enumerate(lines, 1):
# Skip comments
if line.strip().startswith("#"):
continue
# Preload statements
preload_pattern = r'(?:const\s+)?(\w+)?\s*=?\s*preload\s*\(\s*["\']([^"\']+)["\']\s*\)'
# Use finditer to support multiple preloads on a single line and avoid overlapping matches
for match in re.finditer(preload_pattern, line):
const_name = match.group(1)
resource_path = match.group(2)
imports.append(
ImportInfo(
module=resource_path,
alias=const_name,
line=i,
type="preload",
is_relative=resource_path.startswith("res://")
or resource_path.startswith("user://"),
is_resource=True,
resource_type=self._detect_resource_type(resource_path),
)
)
# Load statements (ensure we don't match the 'load' in 'preload')
load_pattern = r"(?<!\w)load\s*\("
for match in re.finditer(load_pattern, line):
# Extract the actual path argument following this 'load('
path_match = re.search(r'\(\s*["\']([^"\']+)["\']\s*\)', line[match.start() :])
if not path_match:
continue
resource_path = path_match.group(1)
imports.append(
ImportInfo(
module=resource_path,
line=i,
type="load",
is_relative=resource_path.startswith("res://")
or resource_path.startswith("user://"),
is_runtime_load=True,
resource_type=self._detect_resource_type(resource_path),
)
)
# Class inheritance (extends)
extends_pattern = r'^\s*extends\s+["\']?([^"\'\s]+)["\']?'
match = re.match(extends_pattern, line)
if match:
parent_class = match.group(1)
# Check if it's a path or class name
is_path = "/" in parent_class or parent_class.endswith(".gd")
imports.append(
ImportInfo(
module=parent_class,
line=i,
type="extends",
is_relative=is_path,
is_inheritance=True,
parent_type="script" if is_path else "class",
)
)
# Class_name declarations (for autoload/global classes)
class_name_pattern = r'^\s*class_name\s+(\w+)(?:\s*,\s*["\']([^"\']+)["\'])?'
match = re.match(class_name_pattern, line)
if match:
class_name = match.group(1)
icon_path = match.group(2)
if icon_path:
imports.append(
ImportInfo(
module=icon_path,
line=i,
type="icon",
is_relative=True,
is_resource=True,
associated_class=class_name,
)
)
# Check for tool script declaration
if re.search(r"^\s*tool\s*$", content, re.MULTILINE):
imports.append(
ImportInfo(
module="@tool",
line=1,
type="tool_mode",
is_relative=False,
is_editor_script=True,
)
)
# Check for @tool annotation (Godot 4.x)
if re.search(r"^\s*@tool\s*$", content, re.MULTILINE):
imports.append(
ImportInfo(
module="@tool",
line=1,
type="annotation",
is_relative=False,
is_editor_script=True,
)
)
return imports
extract_exports¶
Extract exported symbols from GDScript code.
In GDScript, exports include: - class_name declarations (global classes) - export variables - signals - Public functions (by convention, non-underscore prefixed)
| PARAMETER | DESCRIPTION |
|---|---|
content | GDScript source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[Dict[str, Any]] | List of exported symbols |
Source code in tenets/core/analysis/implementations/gdscript_analyzer.py
def extract_exports(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
"""Extract exported symbols from GDScript code.
In GDScript, exports include:
- class_name declarations (global classes)
- export variables
- signals
- Public functions (by convention, non-underscore prefixed)
Args:
content: GDScript source code
file_path: Path to the file being analyzed
Returns:
List of exported symbols
"""
exports = []
# Extract class_name (makes class globally accessible)
class_name_pattern = r'^\s*class_name\s+(\w+)(?:\s*,\s*["\']([^"\']+)["\'])?'
match = re.search(class_name_pattern, content, re.MULTILINE)
if match:
exports.append(
{
"name": match.group(1),
"type": "global_class",
"line": content[: match.start()].count("\n") + 1,
"icon": match.group(2),
"is_autoload_candidate": True,
}
)
# Extract exported variables (Godot 3.x syntax)
export_var_pattern = r"^\s*export(?:\s*\(([^)]*)\))?\s+(?:var\s+)?(\w+)"
for match in re.finditer(export_var_pattern, content, re.MULTILINE):
export_type = match.group(1)
var_name = match.group(2)
exports.append(
{
"name": var_name,
"type": "export_var",
"line": content[: match.start()].count("\n") + 1,
"export_type": export_type,
"inspector_visible": True,
}
)
# Extract exported variables (Godot 4.x syntax with @export)
# Allow optional annotation arguments e.g., @export_range(0,1)
export_annotation_pattern = r"^\s*@export(?:_([a-z_]+))?(?:\([^)]*\))?\s+(?:var\s+)?(\w+)"
for match in re.finditer(export_annotation_pattern, content, re.MULTILINE):
export_modifier = match.group(1)
var_name = match.group(2)
exports.append(
{
"name": var_name,
"type": "export_var",
"line": content[: match.start()].count("\n") + 1,
"export_modifier": export_modifier,
"inspector_visible": True,
"godot_version": 4,
}
)
# Extract signals
signal_pattern = r"^\s*signal\s+(\w+)\s*(?:\(([^)]*)\))?"
for match in re.finditer(signal_pattern, content, re.MULTILINE):
signal_name = match.group(1)
parameters = match.group(2)
exports.append(
{
"name": signal_name,
"type": "signal",
"line": content[: match.start()].count("\n") + 1,
"parameters": self._parse_signal_parameters(parameters),
"is_event": True,
}
)
# Extract public functions (non-underscore prefixed)
func_pattern = r"^\s*(?:static\s+)?func\s+([a-zA-Z]\w*)\s*\("
for match in re.finditer(func_pattern, content, re.MULTILINE):
func_name = match.group(1)
exports.append(
{
"name": func_name,
"type": "function",
"line": content[: match.start()].count("\n") + 1,
"is_public": True,
"is_static": "static" in match.group(0),
}
)
# Extract enums
enum_pattern = r"^\s*enum\s+(\w+)\s*\{"
for match in re.finditer(enum_pattern, content, re.MULTILINE):
exports.append(
{
"name": match.group(1),
"type": "enum",
"line": content[: match.start()].count("\n") + 1,
}
)
# Extract constants (often used as exports in GDScript)
const_pattern = r"^\s*const\s+([A-Z][A-Z0-9_]*)\s*="
for match in re.finditer(const_pattern, content, re.MULTILINE):
exports.append(
{
"name": match.group(1),
"type": "constant",
"line": content[: match.start()].count("\n") + 1,
"is_public": True,
}
)
return exports
extract_structure¶
Extract code structure from GDScript file.
Extracts: - Class inheritance and structure - Inner classes - Functions with type hints - Godot lifecycle methods - Signals and their connections - Export variables - Onready variables - Node references - Setget properties - Enums and constants
| PARAMETER | DESCRIPTION |
|---|---|
content | GDScript source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
CodeStructure | CodeStructure object with extracted elements |
Source code in tenets/core/analysis/implementations/gdscript_analyzer.py
def extract_structure(self, content: str, file_path: Path) -> CodeStructure:
"""Extract code structure from GDScript file.
Extracts:
- Class inheritance and structure
- Inner classes
- Functions with type hints
- Godot lifecycle methods
- Signals and their connections
- Export variables
- Onready variables
- Node references
- Setget properties
- Enums and constants
Args:
content: GDScript source code
file_path: Path to the file being analyzed
Returns:
CodeStructure object with extracted elements
"""
structure = CodeStructure()
# Detect if it's a tool script
structure.is_tool_script = bool(re.search(r"^\s*(?:@)?tool\s*$", content, re.MULTILINE))
# Extract class name
class_name_match = re.search(r"^\s*class_name\s+(\w+)", content, re.MULTILINE)
if class_name_match:
structure.class_name = class_name_match.group(1)
# Extract parent class
extends_match = re.search(r'^\s*extends\s+["\']?([^"\'\s]+)["\']?', content, re.MULTILINE)
if extends_match:
structure.parent_class = extends_match.group(1)
# Detect Godot version (4.x uses @annotations)
structure.godot_version = (
4 if re.search(r"^\s*@(export|onready|tool)", content, re.MULTILINE) else 3
)
# Extract main class info
main_class = ClassInfo(
name=getattr(structure, "class_name", None) or file_path.stem,
line=1,
bases=(
[getattr(structure, "parent_class", None)]
if getattr(structure, "parent_class", None)
else []
),
)
# Extract functions
func_pattern = r"^\s*(?:static\s+)?func\s+(\w+)\s*\(([^)]*)\)(?:\s*->\s*([^:]+))?:"
for match in re.finditer(func_pattern, content, re.MULTILINE):
func_name = match.group(1)
params = match.group(2)
return_type = match.group(3)
is_private = func_name.startswith("_")
is_lifecycle = self._is_lifecycle_method(func_name)
is_virtual = func_name.startswith("_") and not func_name.startswith("__")
func_info = FunctionInfo(
name=func_name,
line=content[: match.start()].count("\n") + 1,
parameters=self._parse_function_parameters(params),
return_type=return_type.strip() if return_type else None,
is_private=is_private,
is_lifecycle=is_lifecycle,
is_virtual=is_virtual,
is_static="static" in content[match.start() - 20 : match.start()],
)
structure.functions.append(func_info)
main_class.methods.append(
{
"name": func_name,
"visibility": "private" if is_private else "public",
"is_lifecycle": is_lifecycle,
}
)
# Extract inner classes
inner_class_pattern = r"^\s*class\s+(\w+)(?:\s+extends\s+([^:]+))?:"
for match in re.finditer(inner_class_pattern, content, re.MULTILINE):
inner_class = ClassInfo(
name=match.group(1),
line=content[: match.start()].count("\n") + 1,
bases=[match.group(2).strip()] if match.group(2) else [],
is_inner=True,
)
structure.classes.append(inner_class)
# Add main class
structure.classes.insert(0, main_class)
# Extract signals
signal_pattern = r"^\s*signal\s+(\w+)\s*(?:\(([^)]*)\))?"
for match in re.finditer(signal_pattern, content, re.MULTILINE):
structure.signals.append(
{
"name": match.group(1),
"line": content[: match.start()].count("\n") + 1,
"parameters": self._parse_signal_parameters(match.group(2)),
}
)
# Extract export variables
# Godot 3.x
export_pattern = r"^\s*export(?:\s*\(([^)]*)\))?\s+(?:var\s+)?(\w+)(?:\s*:\s*([^=\n]+))?(?:\s*=\s*([^\n]+))?"
for match in re.finditer(export_pattern, content, re.MULTILINE):
structure.export_vars.append(
{
"name": match.group(2),
"export_hint": match.group(1),
"type": match.group(3).strip() if match.group(3) else None,
"default": match.group(4).strip() if match.group(4) else None,
"line": content[: match.start()].count("\n") + 1,
}
)
# Godot 4.x
export_4_pattern = r"^\s*@export(?:_([a-z_]+))?(?:\([^)]*\))?\s+(?:var\s+)?(\w+)(?:\s*:\s*([^=\n]+))?(?:\s*=\s*([^\n]+))?"
for match in re.finditer(export_4_pattern, content, re.MULTILINE):
structure.export_vars.append(
{
"name": match.group(2),
"export_modifier": match.group(1),
"type": match.group(3).strip() if match.group(3) else None,
"default": match.group(4).strip() if match.group(4) else None,
"line": content[: match.start()].count("\n") + 1,
"godot_4": True,
}
)
# Extract onready variables
# Godot 3.x
onready_pattern = r"^\s*onready\s+var\s+(\w+)(?:\s*:\s*([^=\n]+))?\s*=\s*([^\n]+)"
for match in re.finditer(onready_pattern, content, re.MULTILINE):
var_name = match.group(1)
var_type = match.group(2)
initialization = match.group(3)
# Check if it's a node reference
is_node_ref = bool(re.search(r"(?:\$|get_node)", initialization))
node_path = self._extract_node_path(initialization)
structure.onready_vars.append(
{
"name": var_name,
"type": var_type.strip() if var_type else None,
"initialization": initialization.strip(),
"is_node_ref": is_node_ref,
"node_path": node_path,
"line": content[: match.start()].count("\n") + 1,
}
)
# Godot 4.x
onready_4_pattern = r"^\s*@onready\s+var\s+(\w+)(?:\s*:\s*([^=\n]+))?\s*=\s*([^\n]+)"
for match in re.finditer(onready_4_pattern, content, re.MULTILINE):
var_name = match.group(1)
var_type = match.group(2)
initialization = match.group(3)
is_node_ref = bool(re.search(r"(?:\$|get_node)", initialization))
node_path = self._extract_node_path(initialization)
structure.onready_vars.append(
{
"name": var_name,
"type": var_type.strip() if var_type else None,
"initialization": initialization.strip(),
"is_node_ref": is_node_ref,
"node_path": node_path,
"line": content[: match.start()].count("\n") + 1,
"godot_4": True,
}
)
# Extract regular variables
var_pattern = r"^\s*var\s+(\w+)(?:\s*:\s*([^=\n]+))?(?:\s*=\s*([^\n]+))?"
for match in re.finditer(var_pattern, content, re.MULTILINE):
# Skip if it's an export or onready var
line_start = content[: match.start()].rfind("\n") + 1
line_content = content[line_start : match.end()]
if "export" in line_content or "onready" in line_content or "@" in line_content:
continue
structure.variables.append(
{
"name": match.group(1),
"type": match.group(2).strip() if match.group(2) else None,
"initial_value": match.group(3).strip() if match.group(3) else None,
"line": content[: match.start()].count("\n") + 1,
}
)
# Extract constants
const_pattern = r"^\s*const\s+(\w+)(?:\s*:\s*([^=\n]+))?\s*=\s*([^\n]+)"
for match in re.finditer(const_pattern, content, re.MULTILINE):
structure.constants.append(
{
"name": match.group(1),
"type": match.group(2).strip() if match.group(2) else None,
"value": match.group(3).strip(),
"line": content[: match.start()].count("\n") + 1,
}
)
# Extract enums
enum_pattern = r"^\s*enum\s+(\w+)\s*\{([^}]+)\}"
for match in re.finditer(enum_pattern, content, re.MULTILINE):
enum_name = match.group(1)
enum_body = match.group(2)
values = self._parse_enum_values(enum_body)
structure.enums.append(
{
"name": enum_name,
"values": values,
"line": content[: match.start()].count("\n") + 1,
}
)
# Extract setget properties
# Support optional setter/getter and missing entries: e.g., setget set_mana or setget , get_level
setget_pattern = r"^\s*var\s+(\w+)(?:[^=\n]*=\s*[^\n]+)?\s+setget\s*(?:([A-Za-z_]\w*)\s*)?(?:,\s*([A-Za-z_]\w*)\s*)?"
for match in re.finditer(setget_pattern, content, re.MULTILINE):
structure.setget_properties.append(
{
"name": match.group(1),
"setter": match.group(2) if match.group(2) else None,
"getter": match.group(3) if match.group(3) else None,
"line": content[: match.start()].count("\n") + 1,
}
)
# Count node references
structure.node_references = len(re.findall(r'\$["\']?[^"\'\s]+["\']?', content))
structure.get_node_calls = len(re.findall(r"get_node\s*\(", content))
# Count signal connections (method form and free function form)
structure.connect_calls = len(re.findall(r"\.connect\s*\(|(?<!\.)\bconnect\s*\(", content))
structure.emit_signal_calls = len(re.findall(r"emit_signal\s*\(", content))
# Detect if it's a custom resource
structure.is_custom_resource = bool(
structure.parent_class and "Resource" in structure.parent_class
)
# Detect if it's an editor plugin
structure.is_editor_plugin = bool(
structure.parent_class and "EditorPlugin" in structure.parent_class
)
return structure
calculate_complexity¶
Calculate complexity metrics for GDScript code.
Calculates: - Cyclomatic complexity - Cognitive complexity - Godot-specific complexity (signals, exports, node references) - Nesting depth - Function count and complexity distribution
| PARAMETER | DESCRIPTION |
|---|---|
content | GDScript source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
ComplexityMetrics | ComplexityMetrics object with calculated metrics |
Source code in tenets/core/analysis/implementations/gdscript_analyzer.py
def calculate_complexity(self, content: str, file_path: Path) -> ComplexityMetrics:
"""Calculate complexity metrics for GDScript code.
Calculates:
- Cyclomatic complexity
- Cognitive complexity
- Godot-specific complexity (signals, exports, node references)
- Nesting depth
- Function count and complexity distribution
Args:
content: GDScript source code
file_path: Path to the file being analyzed
Returns:
ComplexityMetrics object with calculated metrics
"""
metrics = ComplexityMetrics()
# Calculate cyclomatic complexity
complexity = 1
decision_keywords = [
r"\bif\b",
r"\belif\b",
r"\belse\b",
r"\bfor\b",
r"\bwhile\b",
r"\bmatch\b",
r"\bwhen\b",
r"\band\b",
r"\bor\b",
]
for keyword in decision_keywords:
complexity += len(re.findall(keyword, content))
# Each match-case branch contributes to complexity; count simple case labels (numbers, strings, or underscore)
case_label_pattern = r"^\s*(?:_|-?\d+|\"[^\"\n]+\"|\'[^\'\n]+\')\s*:"
complexity += len(re.findall(case_label_pattern, content, re.MULTILINE))
# Inline lambda expressions (func(...) :) add decision/branching potential
lambda_inline_pattern = (
r"func\s*\(" # named functions are 'func name(', lambdas are 'func(' directly
)
complexity += len(re.findall(lambda_inline_pattern, content))
metrics.cyclomatic = complexity
# Calculate cognitive complexity
cognitive = 0
nesting_level = 0
max_nesting = 0
lines = content.split("\n")
for line in lines:
# Skip comments
if line.strip().startswith("#"):
continue
# Track nesting by indentation (GDScript uses indentation)
if line.strip():
indent = len(line) - len(line.lstrip())
# Assuming tab or 4 spaces as one level
if "\t" in line[:indent]:
current_level = line[:indent].count("\t")
else:
current_level = indent // 4
max_nesting = max(max_nesting, current_level)
# Control structures with nesting penalty
control_patterns = [
(r"\bif\b", 1),
(r"\belif\b", 1),
(r"\belse\b", 0),
(r"\bfor\b", 1),
(r"\bwhile\b", 1),
(r"\bmatch\b", 1),
]
for pattern, weight in control_patterns:
if re.search(pattern, line):
cognitive += weight * (1 + max(0, current_level))
metrics.cognitive = cognitive
metrics.max_depth = max_nesting
# Count code elements
metrics.line_count = len(lines)
metrics.code_lines = len([l for l in lines if l.strip() and not l.strip().startswith("#")])
metrics.comment_lines = len([l for l in lines if l.strip().startswith("#")])
metrics.comment_ratio = (
metrics.comment_lines / metrics.line_count if metrics.line_count > 0 else 0
)
# Count functions
metrics.function_count = len(re.findall(r"\bfunc\s+\w+", content))
# Count classes
metrics.class_count = len(re.findall(r"\bclass\s+\w+", content))
metrics.class_count += 1 if re.search(r"^\s*extends\s+", content, re.MULTILINE) else 0
# Godot-specific metrics
metrics.signal_count = len(re.findall(r"\bsignal\s+\w+", content))
metrics.export_count = len(re.findall(r"(?:@)?export(?:_\w+)?(?:\([^)]*\))?\s+", content))
metrics.onready_count = len(re.findall(r"(?:@)?onready\s+var", content))
# Node reference metrics
metrics.node_ref_count = len(re.findall(r'\$["\']?[^"\'\s]+["\']?', content))
metrics.get_node_count = len(re.findall(r"get_node\s*\(", content))
# Signal connection metrics
metrics.connect_count = len(re.findall(r"\.connect\s*\(|(?<!\.)\bconnect\s*\(", content))
metrics.emit_count = len(re.findall(r"emit_signal\s*\(", content))
# Lifecycle method count
lifecycle_methods = [
"_ready",
"_enter_tree",
"_exit_tree",
"_process",
"_physics_process",
"_input",
"_unhandled_input",
"_draw",
"_gui_input",
"_notification",
]
metrics.lifecycle_count = sum(
1 for method in lifecycle_methods if re.search(rf"\bfunc\s+{method}\s*\(", content)
)
# RPC/Networking metrics
metrics.rpc_count = len(
re.findall(r"@rpc|rpc\(|rpc_unreliable\(|remotesync\s+func", content)
)
# Type hints metrics
metrics.typed_vars = len(re.findall(r"(?:var|const)\s+\w+\s*:\s*\w+", content))
metrics.typed_funcs = len(re.findall(r"func\s+\w+\s*\([^)]*:\s*\w+[^)]*\)", content))
metrics.return_types = len(re.findall(r"\)\s*->\s*\w+\s*:", content))
# Calculate Godot-specific complexity score
godot_complexity = (
metrics.signal_count * 2
+ metrics.export_count
+ metrics.onready_count
+ metrics.node_ref_count * 0.5
+ metrics.connect_count * 2
+ metrics.emit_count
)
# Calculate maintainability index
import math
if metrics.code_lines > 0:
# Adjusted for GDScript
godot_factor = 1 - (godot_complexity * 0.001)
type_factor = 1 + (metrics.typed_vars + metrics.typed_funcs) * 0.001
mi = (
171
- 5.2 * math.log(max(1, complexity))
- 0.23 * complexity
- 16.2 * math.log(metrics.code_lines)
+ 10 * godot_factor
+ 5 * type_factor
)
metrics.maintainability_index = max(0, min(100, mi))
return metrics
GenericAnalyzer¶
Bases: LanguageAnalyzer
Generic analyzer for unsupported file types.
Provides basic analysis for text-based files including: - Line and character counting - Basic pattern matching for imports/includes - Simple complexity estimation - Keyword extraction - Configuration file parsing (JSON, YAML, XML, etc.)
This analyzer serves as a fallback for files without specific language support and can handle various text formats.
Initialize the generic analyzer with logger.
Source code in tenets/core/analysis/implementations/generic_analyzer.py
extract_imports¶
Extract potential imports/includes from generic text.
Looks for common import patterns across various languages and configuration files.
| PARAMETER | DESCRIPTION |
|---|---|
content | File content TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[ImportInfo] | List of ImportInfo objects with detected imports |
Source code in tenets/core/analysis/implementations/generic_analyzer.py
def extract_imports(self, content: str, file_path: Path) -> List[ImportInfo]:
"""Extract potential imports/includes from generic text.
Looks for common import patterns across various languages
and configuration files.
Args:
content: File content
file_path: Path to the file being analyzed
Returns:
List of ImportInfo objects with detected imports
"""
imports = []
lines = content.split("\n")
# Common import/include patterns
patterns = [
# Include patterns (C-style, various scripting languages)
(r"^\s*#include\s+<([^>]+)>", "include"), # angle includes
(r'^\s*#include\s+"([^"]+)"', "include"), # quote includes
(r"^\s*include\s+[\'\"]([^\'\"]+)[\'\"]", "include"),
# CMake include()
(r"^\s*include\s*\(\s*([^)\s]+)\s*\)", "include"),
# Import patterns (various languages)
(r'^\s*import\s+[\'"]([^\'"]+)[\'"]', "import"), # import "module"
(r"^\s*import\s+([A-Za-z_][\w\.]*)\b", "import"), # import os
(r'^\s*from\s+[\'"]([^\'"]+)[\'"]', "from"), # from "mod"
(r"^\s*from\s+([A-Za-z_][\w\.]*)\s+import\b", "from"), # from pkg import X
(r'^\s*require\s+[\'"]([^\'"]+)[\'"]', "require"),
# PHP/Perl and JS style use statements
(r"^\s*use\s+([\\\w:]+);?", "use"), # use Data::Dumper; or use Foo\Bar;
# Load/source patterns (shell scripts)
(r'^\s*source\s+[\'"]?([^\'"]+)[\'"]?', "source"),
(r'^\s*\.[ \t]+[\'"]?([^\'"]+)[\'"]?', "source"),
# Configuration file references
(r'[\'"]?(?:file|path|src|href|url)[\'"]?\s*[:=]\s*[\'"]([^\'"]+)[\'"]', "reference"),
]
captured_modules: set[str] = set()
for i, line in enumerate(lines, 1):
# Skip comments (generic comment patterns) but keep C preprocessor includes
if (
line.strip().startswith("#") and not re.match(r"^\s*#include\b", line)
) or line.strip().startswith("//"):
continue
for pattern, import_type in patterns:
match = re.search(pattern, line, re.IGNORECASE)
if match:
module = match.group(1)
imports.append(
ImportInfo(
module=module,
line=i,
type=import_type,
is_relative=self._is_relative_path(module),
)
)
captured_modules.add(module)
break
# Special case: 'use strict;' (JavaScript directive)
if re.match(r"^\s*use\s+strict\s*;?\s*$", line):
imports.append(ImportInfo(module="strict", line=i, type="use", is_relative=False))
captured_modules.add("strict")
# Special handling for specific file types
if file_path.suffix.lower() in [".json", ".yaml", ".yml"]:
imports.extend(self._extract_config_dependencies(content, file_path))
# Detect standalone file references like config.yml in logs
file_ref_pattern = re.compile(
r"\b([\w./-]+\.(?:ya?ml|json|conf|cfg|ini|xml|toml|log|txt|sh))\b"
)
for i, line in enumerate(lines, 1):
for m in file_ref_pattern.finditer(line):
module = m.group(1)
if module not in captured_modules:
imports.append(
ImportInfo(
module=module,
line=i,
type="reference",
is_relative=self._is_relative_path(module),
)
)
captured_modules.add(module)
return imports
extract_exports¶
Extract potential exports from generic text.
Looks for common export patterns and definitions.
| PARAMETER | DESCRIPTION |
|---|---|
content | File content TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[Dict[str, Any]] | List of potential exported symbols |
Source code in tenets/core/analysis/implementations/generic_analyzer.py
def extract_exports(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
"""Extract potential exports from generic text.
Looks for common export patterns and definitions.
Args:
content: File content
file_path: Path to the file being analyzed
Returns:
List of potential exported symbols
"""
exports = []
# Common export/definition patterns
patterns = [
# Function-like definitions
(r"^(?:function|def|func|sub|proc)\s+(\w+)", "function"),
(r"^(\w+)\s*\(\)\s*\{", "function"),
# Class-like definitions
(r"^(?:class|struct|type|interface)\s+(\w+)", "class"),
# Variable/constant definitions
(r"^(?:export\s+)?(?:const|let|var|val)\s+(\w+)\s*=", "variable"),
(r'^(\w+)\s*=\s*[\'"]?[^\'"\n]+[\'"]?', "assignment"),
# Export statements
(r"^export\s+(\w+)", "export"),
(r"^module\.exports\.(\w+)", "export"),
]
for pattern, export_type in patterns:
for match in re.finditer(pattern, content, re.MULTILINE):
name = match.group(1)
exports.append(
{
"name": name,
"type": export_type,
"line": content[: match.start()].count("\n") + 1,
}
)
# For configuration files, extract top-level keys
if file_path.suffix.lower() in [".json", ".yaml", ".yml", ".toml", ".ini"]:
exports.extend(self._extract_config_keys(content, file_path))
return exports
extract_structure¶
Extract basic structure from generic text.
Attempts to identify structural elements using pattern matching and indentation analysis.
| PARAMETER | DESCRIPTION |
|---|---|
content | File content TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
CodeStructure | CodeStructure object with detected elements |
Source code in tenets/core/analysis/implementations/generic_analyzer.py
def extract_structure(self, content: str, file_path: Path) -> CodeStructure:
"""Extract basic structure from generic text.
Attempts to identify structural elements using pattern matching
and indentation analysis.
Args:
content: File content
file_path: Path to the file being analyzed
Returns:
CodeStructure object with detected elements
"""
structure = CodeStructure()
# Detect file type category
file_type = self._detect_file_type(file_path)
structure.file_type = file_type
# Detect common YAML-based frameworks/configs
try:
if file_path.suffix.lower() in [".yaml", ".yml"]:
# Initialize modules collection if not present
if not hasattr(structure, "modules"):
structure.modules = []
if self._is_docker_compose_file(file_path, content):
structure.framework = "docker-compose"
for svc in self._extract_compose_services(content):
structure.modules.append({"type": "service", **svc})
elif self._looks_like_kubernetes_yaml(content):
structure.framework = "kubernetes"
for res in self._extract_k8s_resources(content):
structure.modules.append({"type": "resource", **res})
else:
# Helm/Kustomize/GitHub Actions quick hints
name = file_path.name.lower()
if name == "chart.yaml":
structure.framework = "helm"
elif name == "values.yaml":
structure.framework = getattr(structure, "framework", None) or "helm"
elif name == "kustomization.yaml":
structure.framework = "kustomize"
elif ".github" in str(file_path).replace("\\", "/") and "/workflows/" in str(
file_path
).replace("\\", "/"):
structure.framework = "github-actions"
except Exception:
# Never fail generic structure on heuristics
pass
# Extract functions (various patterns)
function_patterns = [
r"^(?:async\s+)?(?:function|def|func|sub|proc)\s+(\w+)",
r"^(\w+)\s*\(\)\s*\{",
r"^(\w+)\s*:\s*function",
r"^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>",
]
for pattern in function_patterns:
for match in re.finditer(pattern, content, re.MULTILINE):
func_name = match.group(1)
structure.functions.append(
FunctionInfo(name=func_name, line=content[: match.start()].count("\n") + 1)
)
# Extract classes/types
class_patterns = [
r"^(?:export\s+)?(?:class|struct|type|interface|enum)\s+(\w+)",
r"^(\w+)\s*=\s*class\s*\{",
]
for pattern in class_patterns:
for match in re.finditer(pattern, content, re.MULTILINE):
class_name = match.group(1)
structure.classes.append(
ClassInfo(name=class_name, line=content[: match.start()].count("\n") + 1)
)
# Extract sections (markdown headers, etc.)
if file_type in ["markdown", "documentation", "markup"]:
section_pattern = r"^(#{1,6})\s+(.+)$"
for match in re.finditer(section_pattern, content, re.MULTILINE):
level = len(match.group(1))
title = match.group(2)
structure.sections.append(
{
"title": title,
"level": level,
"line": content[: match.start()].count("\n") + 1,
}
)
# Extract variables/constants
var_patterns = [
r"^(?:const|let|var|val)\s+(\w+)",
r"^(\w+)\s*[:=]\s*[^=]",
r"^export\s+(\w+)",
]
for pattern in var_patterns:
for match in re.finditer(pattern, content, re.MULTILINE):
var_name = match.group(1)
structure.variables.append(
{
"name": var_name,
"line": content[: match.start()].count("\n") + 1,
"type": "variable",
}
)
# Detect constants (UPPERCASE variables)
const_pattern = r"^([A-Z][A-Z0-9_]*)\s*[:=]"
for match in re.finditer(const_pattern, content, re.MULTILINE):
structure.constants.append(match.group(1))
# Extract TODO/FIXME comments
todo_pattern = r"(?:#|//|/\*|\*)\s*(TODO|FIXME|HACK|NOTE|XXX|BUG):\s*(.+)"
for match in re.finditer(todo_pattern, content, re.IGNORECASE):
structure.todos.append(
{
"type": match.group(1).upper(),
"message": match.group(2).strip(),
"line": content[: match.start()].count("\n") + 1,
}
)
# Count blocks (based on indentation or braces)
structure.block_count = content.count("{")
structure.indent_levels = self._analyze_indentation(content)
return structure
calculate_complexity¶
Calculate basic complexity metrics for generic text.
Provides simplified complexity estimation based on: - Line count and length - Nesting depth (indentation/braces) - Decision keywords - File type specific metrics
| PARAMETER | DESCRIPTION |
|---|---|
content | File content TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
ComplexityMetrics | ComplexityMetrics object with basic metrics |
Source code in tenets/core/analysis/implementations/generic_analyzer.py
def calculate_complexity(self, content: str, file_path: Path) -> ComplexityMetrics:
"""Calculate basic complexity metrics for generic text.
Provides simplified complexity estimation based on:
- Line count and length
- Nesting depth (indentation/braces)
- Decision keywords
- File type specific metrics
Args:
content: File content
file_path: Path to the file being analyzed
Returns:
ComplexityMetrics object with basic metrics
"""
metrics = ComplexityMetrics()
# Basic line metrics
lines = content.split("\n")
# Trim leading/trailing empty lines for line count to match human expectations/tests
start = 0
end = len(lines)
while start < end and lines[start].strip() == "":
start += 1
while end > start and lines[end - 1].strip() == "":
end -= 1
trimmed_lines = lines[start:end]
# Preserve historical/test expectation: an entirely empty file counts as 1 line (logical line),
# while code_lines will be 0. Non-empty (after trimming) counts actual trimmed lines.
if not trimmed_lines:
metrics.line_count = 1
else:
metrics.line_count = len(trimmed_lines)
# Character count: count characters, and if file doesn't end with newline, count implicit final EOL
metrics.character_count = len(content) + (0 if content.endswith("\n") else 1)
# Count comment lines (generic patterns)
comment_patterns = [
r"^\s*#", # Hash comments
r"^\s*//", # Double slash comments
r"^\s*/\*", # Block comment start
r"^\s*\*", # Block comment continuation
r"^\s*<!--", # HTML/XML comments
r"^\s*;", # Semicolon comments (INI, assembly)
r"^\s*--", # SQL/Lua comments
r"^\s*%", # LaTeX/MATLAB comments
]
comment_lines = 0
for line in trimmed_lines:
if any(re.match(pattern, line) for pattern in comment_patterns):
comment_lines += 1
# Compute code lines as total lines minus comment lines (consistent with tests)
# For empty file (line_count==1 but no trimmed lines), code_lines should be 0
if not trimmed_lines:
metrics.code_lines = 0
else:
metrics.code_lines = metrics.line_count - comment_lines
metrics.comment_lines = comment_lines
metrics.comment_ratio = comment_lines / metrics.line_count if metrics.line_count > 0 else 0
# Estimate cyclomatic complexity (decision points)
decision_keywords = [
r"\bif\b",
r"\belse\b",
r"\belif\b",
r"\belsif\b",
r"\bfor\b",
r"\bwhile\b",
r"\bdo\b",
r"\bcase\b",
r"\bwhen\b",
r"\btry\b",
r"\bcatch\b",
r"\bexcept\b",
r"\bunless\b",
r"\buntil\b",
r"\bswitch\b",
r"\b\?\s*[^:]+\s*:",
r"\|\|",
r"&&",
r"\band\b",
r"\bor\b",
]
complexity = 1 # Base complexity
for keyword in decision_keywords:
complexity += len(re.findall(keyword, content, re.IGNORECASE))
metrics.cyclomatic = min(complexity, 50) # Cap at 50 for generic files
# Estimate nesting depth
max_depth = 0
current_depth = 0
for line in lines:
# Track braces
current_depth += line.count("{") - line.count("}")
current_depth += line.count("(") - line.count(")")
current_depth += line.count("[") - line.count("]")
max_depth = max(max_depth, current_depth)
# Reset if negative (mismatched brackets)
current_depth = max(current_depth, 0)
# Also check indentation depth
indent_depth = self._calculate_max_indent(lines)
# Combine and cap at 10
metrics.max_depth = min(max(max_depth, indent_depth), 10)
# File type specific metrics
file_type = self._detect_file_type(file_path)
if file_type == "configuration":
# For config files, count keys/sections
metrics.key_count = len(re.findall(r"^\s*[\w\-\.]+\s*[:=]", content, re.MULTILINE))
metrics.section_count = len(re.findall(r"^\s*\[[\w\-\.]+\]", content, re.MULTILINE))
elif file_type == "markup":
# For markup files, count tags
metrics.tag_count = len(re.findall(r"<\w+", content))
metrics.header_count = len(re.findall(r"^#{1,6}\s+", content, re.MULTILINE))
elif file_type == "data":
# For data files, estimate structure
if file_path.suffix.lower() == ".csv":
lines_sample = lines[:10] if len(lines) > 10 else lines
if lines_sample:
# Estimate columns
metrics.column_count = len(lines_sample[0].split(","))
metrics.row_count = len(lines) - 1 # Exclude header
# Calculate a simple maintainability index
if metrics.code_lines > 0:
# Simplified calculation
maintainability = 100
# Penalize high complexity
maintainability -= min(30, complexity * 0.5)
# Penalize deep nesting
maintainability -= min(20, metrics.max_depth * 2)
# Reward comments
maintainability += min(10, metrics.comment_ratio * 30)
# Penalize very long files
if metrics.line_count > 1000:
maintainability -= 10
elif metrics.line_count > 500:
maintainability -= 5
metrics.maintainability_index = max(0, min(100, maintainability))
return metrics
extract_context_relevant_sections¶
extract_context_relevant_sections(content: str, file_path: Path, prompt_keywords: List[str], search_depth: int = 2, min_confidence: float = 0.6, max_sections: int = 10) -> Dict[str, Any]
Extract sections of documentation that reference prompt keywords/concepts.
This method identifies and extracts the most relevant parts of documentation files based on direct references and semantic similarity to prompt keywords.
| PARAMETER | DESCRIPTION |
|---|---|
content | File content TYPE: |
file_path | Path to the file being analyzed TYPE: |
prompt_keywords | Keywords/phrases from the user's prompt |
search_depth | How deep to search (1=direct, 2=semantic, 3=deep analysis) TYPE: |
min_confidence | Minimum confidence threshold for relevance (0.0-1.0) TYPE: |
max_sections | Maximum number of contextual sections to preserve TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
Dict[str, Any] | Dictionary containing relevant sections with metadata |
Source code in tenets/core/analysis/implementations/generic_analyzer.py
def extract_context_relevant_sections(
self,
content: str,
file_path: Path,
prompt_keywords: List[str],
search_depth: int = 2,
min_confidence: float = 0.6,
max_sections: int = 10,
) -> Dict[str, Any]:
"""Extract sections of documentation that reference prompt keywords/concepts.
This method identifies and extracts the most relevant parts of documentation
files based on direct references and semantic similarity to prompt keywords.
Args:
content: File content
file_path: Path to the file being analyzed
prompt_keywords: Keywords/phrases from the user's prompt
search_depth: How deep to search (1=direct, 2=semantic, 3=deep analysis)
min_confidence: Minimum confidence threshold for relevance (0.0-1.0)
max_sections: Maximum number of contextual sections to preserve
Returns:
Dictionary containing relevant sections with metadata
"""
if not prompt_keywords:
return {
"relevant_sections": [],
"metadata": {"total_sections": 0, "matched_sections": 0},
}
file_type = self._detect_file_type(file_path)
# Extract sections based on file type
sections = self._extract_document_sections(content, file_path, file_type)
# Score sections based on relevance to prompt keywords
scored_sections = []
for section in sections:
score, matches = self._calculate_section_relevance(
section, prompt_keywords, search_depth
)
if score >= min_confidence:
scored_sections.append(
{
**section,
"relevance_score": score,
"keyword_matches": matches,
"context_type": self._determine_context_type(section, matches),
}
)
# Sort by relevance and limit to max_sections
scored_sections.sort(key=lambda x: x["relevance_score"], reverse=True)
relevant_sections = scored_sections[:max_sections]
# Extract code examples and references within relevant sections
for section in relevant_sections:
section["code_examples"] = self._extract_code_examples_from_section(section)
section["api_references"] = self._extract_api_references_from_section(section)
section["config_references"] = self._extract_config_references_from_section(section)
metadata = {
"total_sections": len(sections),
"matched_sections": len(scored_sections),
"relevant_sections": len(relevant_sections),
"file_type": file_type,
"search_depth": search_depth,
"min_confidence": min_confidence,
"avg_relevance_score": (
sum(s["relevance_score"] for s in relevant_sections) / len(relevant_sections)
if relevant_sections
else 0.0
),
}
return {"relevant_sections": relevant_sections, "metadata": metadata}
GoAnalyzer¶
Bases: LanguageAnalyzer
Go code analyzer.
Provides comprehensive analysis for Go files including: - Import analysis with vendored and internal imports - Function, method and interface extraction - Struct analysis with embedded types - Goroutine and channel detection - Error handling patterns - Defer statement tracking - Package-level analysis - Go module support
Go's export mechanism is based on capitalization - identifiers starting with uppercase letters are exported.
Initialize the Go analyzer with logger.
Source code in tenets/core/analysis/implementations/go_analyzer.py
extract_imports¶
Extract imports from Go code.
Handles: - Single imports: import "fmt" - Grouped imports: import ( "fmt" "strings" ) - Aliased imports: import f "fmt" - Dot imports: import . "fmt" - Blank imports: import _ "database/sql" - Vendored imports - Internal packages
| PARAMETER | DESCRIPTION |
|---|---|
content | Go source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[ImportInfo] | List of ImportInfo objects with import details |
Source code in tenets/core/analysis/implementations/go_analyzer.py
def extract_imports(self, content: str, file_path: Path) -> List[ImportInfo]:
"""Extract imports from Go code.
Handles:
- Single imports: import "fmt"
- Grouped imports: import ( "fmt" "strings" )
- Aliased imports: import f "fmt"
- Dot imports: import . "fmt"
- Blank imports: import _ "database/sql"
- Vendored imports
- Internal packages
Args:
content: Go source code
file_path: Path to the file being analyzed
Returns:
List of ImportInfo objects with import details
"""
imports = []
lines = content.split("\n")
import_block = False
import_block_start = 0
for i, line in enumerate(lines, 1):
# Skip comments
if line.strip().startswith("//"):
continue
# Single import statement
single_import = re.match(r'^\s*import\s+"([^"]+)"', line)
if single_import:
imports.append(
ImportInfo(
module=single_import.group(1),
line=i,
type="import",
is_relative=False,
is_vendored=self._is_vendored_import(single_import.group(1)),
is_internal="internal" in single_import.group(1),
)
)
continue
# Aliased single import
aliased_import = re.match(r'^\s*import\s+(\w+)\s+"([^"]+)"', line)
if aliased_import:
imports.append(
ImportInfo(
module=aliased_import.group(2),
alias=aliased_import.group(1),
line=i,
type="aliased",
is_relative=False,
is_vendored=self._is_vendored_import(aliased_import.group(2)),
)
)
continue
# Dot import
dot_import = re.match(r'^\s*import\s+\.\s+"([^"]+)"', line)
if dot_import:
imports.append(
ImportInfo(
module=dot_import.group(1),
alias=".",
line=i,
type="dot_import",
is_relative=False,
)
)
continue
# Blank import
blank_import = re.match(r'^\s*import\s+_\s+"([^"]+)"', line)
if blank_import:
imports.append(
ImportInfo(
module=blank_import.group(1),
alias="_",
line=i,
type="blank_import",
is_relative=False,
purpose="side_effects",
)
)
continue
# Import block start
if re.match(r"^\s*import\s*\(", line):
import_block = True
import_block_start = i
continue
# Inside import block
if import_block:
# Check for end of import block
if ")" in line:
import_block = False
continue
# Standard import in block
standard_import = re.match(r'^\s*"([^"]+)"', line)
if standard_import:
module = standard_import.group(1)
imports.append(
ImportInfo(
module=module,
line=i,
type="import",
is_relative=False,
is_vendored=self._is_vendored_import(module),
is_internal="internal" in module,
is_stdlib=self._is_stdlib_import(module),
)
)
continue
# Aliased import in block
aliased_import = re.match(r'^\s*(\w+)\s+"([^"]+)"', line)
if aliased_import:
module = aliased_import.group(2)
imports.append(
ImportInfo(
module=module,
alias=aliased_import.group(1),
line=i,
type="aliased",
is_relative=False,
is_vendored=self._is_vendored_import(module),
)
)
continue
# Dot import in block
dot_import = re.match(r'^\s*\.\s+"([^"]+)"', line)
if dot_import:
imports.append(
ImportInfo(
module=dot_import.group(1),
alias=".",
line=i,
type="dot_import",
is_relative=False,
)
)
continue
# Blank import in block
blank_import = re.match(r'^\s*_\s+"([^"]+)"', line)
if blank_import:
imports.append(
ImportInfo(
module=blank_import.group(1),
alias="_",
line=i,
type="blank_import",
is_relative=False,
purpose="side_effects",
)
)
# Categorize imports
self._categorize_imports(imports)
return imports
extract_exports¶
Extract exported symbols from Go code.
In Go, exported identifiers start with an uppercase letter. This includes functions, types, constants, and variables.
| PARAMETER | DESCRIPTION |
|---|---|
content | Go source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[Dict[str, Any]] | List of exported symbols with metadata |
Source code in tenets/core/analysis/implementations/go_analyzer.py
def extract_exports(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
"""Extract exported symbols from Go code.
In Go, exported identifiers start with an uppercase letter.
This includes functions, types, constants, and variables.
Args:
content: Go source code
file_path: Path to the file being analyzed
Returns:
List of exported symbols with metadata
"""
exports = []
# Extract package name
package_match = re.search(r"^\s*package\s+(\w+)", content, re.MULTILINE)
package_name = package_match.group(1) if package_match else "unknown"
# Exported functions
func_pattern = (
r"^func\s+([A-Z][a-zA-Z0-9]*)\s*\(([^)]*)\)(?:\s*\(([^)]*)\))?(?:\s*([^{]+))?\s*\{"
)
for match in re.finditer(func_pattern, content, re.MULTILINE):
func_name = match.group(1)
params = match.group(2)
return_params = match.group(3)
return_type = match.group(4)
exports.append(
{
"name": func_name,
"type": "function",
"line": content[: match.start()].count("\n") + 1,
"package": package_name,
"signature": self._build_function_signature(
func_name, params, return_params, return_type
),
"has_receiver": False,
}
)
# Exported methods (with receivers)
method_pattern = r"^func\s+\(([^)]+)\)\s+([A-Z][a-zA-Z0-9]*)\s*\(([^)]*)\)(?:\s*\(([^)]*)\))?(?:\s*([^{]+))?\s*\{"
for match in re.finditer(method_pattern, content, re.MULTILINE):
receiver = match.group(1)
method_name = match.group(2)
params = match.group(3)
return_params = match.group(4)
return_type = match.group(5)
# Parse receiver type
receiver_type = self._parse_receiver(receiver)
exports.append(
{
"name": method_name,
"type": "method",
"line": content[: match.start()].count("\n") + 1,
"receiver": receiver_type,
"package": package_name,
"signature": self._build_method_signature(
receiver, method_name, params, return_params, return_type
),
"has_receiver": True,
}
)
# Exported types (structs, interfaces, type aliases)
type_pattern = r"^type\s+([A-Z][a-zA-Z0-9]*)\s+(.+?)(?:\n|\{)"
for match in re.finditer(type_pattern, content, re.MULTILINE):
type_name = match.group(1)
type_def = match.group(2).strip()
# Determine type kind
if "struct" in type_def:
type_kind = "struct"
elif "interface" in type_def:
type_kind = "interface"
elif "=" in type_def:
type_kind = "alias"
else:
type_kind = "type"
exports.append(
{
"name": type_name,
"type": type_kind,
"line": content[: match.start()].count("\n") + 1,
"package": package_name,
"definition": type_def[:50] if len(type_def) > 50 else type_def,
}
)
# Exported constants
const_pattern = r"^const\s+([A-Z][a-zA-Z0-9]*)\s*(?:[\w\s]+)?\s*="
for match in re.finditer(const_pattern, content, re.MULTILINE):
exports.append(
{
"name": match.group(1),
"type": "constant",
"line": content[: match.start()].count("\n") + 1,
"package": package_name,
}
)
# Exported constant blocks
const_block_pattern = r"^const\s*\((.*?)\)"
for match in re.finditer(const_block_pattern, content, re.MULTILINE | re.DOTALL):
block_content = match.group(1)
for const_match in re.finditer(r"^\s*([A-Z][a-zA-Z0-9]*)", block_content, re.MULTILINE):
exports.append(
{
"name": const_match.group(1),
"type": "constant",
"line": content[: match.start()].count("\n")
+ block_content[: const_match.start()].count("\n")
+ 1,
"package": package_name,
"in_block": True,
}
)
# Exported variables
var_pattern = r"^var\s+([A-Z][a-zA-Z0-9]*)\s+"
for match in re.finditer(var_pattern, content, re.MULTILINE):
exports.append(
{
"name": match.group(1),
"type": "variable",
"line": content[: match.start()].count("\n") + 1,
"package": package_name,
}
)
return exports
extract_structure¶
Extract code structure from Go file.
Extracts: - Package declaration - Functions and methods - Structs (treated as classes) - Interfaces - Type aliases - Constants and variables - Goroutines and channels - Init functions
| PARAMETER | DESCRIPTION |
|---|---|
content | Go source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
CodeStructure | CodeStructure object with extracted elements |
Source code in tenets/core/analysis/implementations/go_analyzer.py
def extract_structure(self, content: str, file_path: Path) -> CodeStructure:
"""Extract code structure from Go file.
Extracts:
- Package declaration
- Functions and methods
- Structs (treated as classes)
- Interfaces
- Type aliases
- Constants and variables
- Goroutines and channels
- Init functions
Args:
content: Go source code
file_path: Path to the file being analyzed
Returns:
CodeStructure object with extracted elements
"""
structure = CodeStructure()
# Extract package name
package_match = re.search(r"^\s*package\s+(\w+)", content, re.MULTILINE)
if package_match:
structure.package = package_match.group(1)
structure.is_main = structure.package == "main"
# Extract functions
func_pattern = (
r"^func\s+(?:\([^)]+\)\s+)?(\w+)\s*\(([^)]*)\)(?:\s*\(([^)]*)\))?(?:\s*([^{]+))?"
)
for match in re.finditer(func_pattern, content, re.MULTILINE):
func_name = match.group(1)
params = match.group(2)
# Check for special functions
is_init = func_name == "init"
is_main = func_name == "main" and structure.is_main
is_test = func_name.startswith("Test") or func_name.startswith("Benchmark")
func_info = FunctionInfo(
name=func_name,
line=content[: match.start()].count("\n") + 1,
args=self._parse_go_params(params),
is_exported=func_name[0].isupper(),
is_init=is_init,
is_main=is_main,
is_test=is_test,
)
structure.functions.append(func_info)
# Extract structs (as classes)
struct_pattern = r"^type\s+(\w+)\s+struct\s*\{"
for match in re.finditer(struct_pattern, content, re.MULTILINE):
struct_name = match.group(1)
# Find struct fields
struct_start = match.end()
brace_count = 1
struct_end = struct_start
for i, char in enumerate(content[struct_start:], struct_start):
if char == "{":
brace_count += 1
elif char == "}":
brace_count -= 1
if brace_count == 0:
struct_end = i
break
struct_content = content[struct_start:struct_end]
fields = self._extract_struct_fields(struct_content)
# Find methods for this struct
methods = self._find_struct_methods(content, struct_name)
class_info = ClassInfo(
name=struct_name,
line=content[: match.start()].count("\n") + 1,
is_exported=struct_name[0].isupper(),
fields=fields,
methods=methods,
embedded_types=self._find_embedded_types(struct_content),
)
structure.classes.append(class_info)
# Extract interfaces
interface_pattern = r"^type\s+(\w+)\s+interface\s*\{"
for match in re.finditer(interface_pattern, content, re.MULTILINE):
interface_name = match.group(1)
# Find interface methods
interface_start = match.end()
brace_count = 1
interface_end = interface_start
for i, char in enumerate(content[interface_start:], interface_start):
if char == "{":
brace_count += 1
elif char == "}":
brace_count -= 1
if brace_count == 0:
interface_end = i
break
interface_content = content[interface_start:interface_end]
methods = self._extract_interface_methods(interface_content)
structure.interfaces.append(
{
"name": interface_name,
"line": content[: match.start()].count("\n") + 1,
"is_exported": interface_name[0].isupper(),
"methods": methods,
"is_empty": len(methods) == 0, # Empty interface (interface{})
}
)
# Extract type aliases
type_alias_pattern = r"^type\s+(\w+)\s*=\s*(.+)$"
for match in re.finditer(type_alias_pattern, content, re.MULTILINE):
structure.type_aliases.append(
{
"name": match.group(1),
"base_type": match.group(2).strip(),
"line": content[: match.start()].count("\n") + 1,
"is_exported": match.group(1)[0].isupper(),
}
)
# Extract custom type definitions
type_def_pattern = r"^type\s+(\w+)\s+(\w+)$"
for match in re.finditer(type_def_pattern, content, re.MULTILINE):
if not re.match(r"^type\s+\w+\s+(?:struct|interface)", content[match.start() :]):
structure.type_definitions.append(
{
"name": match.group(1),
"base_type": match.group(2),
"line": content[: match.start()].count("\n") + 1,
"is_exported": match.group(1)[0].isupper(),
}
)
# Extract constants
const_pattern = r"^const\s+(\w+)"
for match in re.finditer(const_pattern, content, re.MULTILINE):
const_name = match.group(1)
structure.constants.append(const_name)
structure.variables.append(
{
"name": const_name,
"line": content[: match.start()].count("\n") + 1,
"type": "constant",
"is_exported": const_name[0].isupper(),
}
)
# Extract variables
var_pattern = r"^var\s+(\w+)"
for match in re.finditer(var_pattern, content, re.MULTILINE):
var_name = match.group(1)
structure.variables.append(
{
"name": var_name,
"line": content[: match.start()].count("\n") + 1,
"type": "variable",
"is_exported": var_name[0].isupper(),
}
)
# Count goroutines
goroutine_pattern = r"\bgo\s+(?:\w+\.)*\w+\s*\("
structure.goroutines_count = len(re.findall(goroutine_pattern, content))
# Count channels
channel_pattern = r"(?:chan\s+\w+|<-chan\s+\w+|chan<-\s+\w+)"
structure.channels_count = len(re.findall(channel_pattern, content))
# Count defer statements
defer_pattern = r"\bdefer\s+"
structure.defer_count = len(re.findall(defer_pattern, content))
# Detect test file
structure.is_test_file = file_path.name.endswith("_test.go")
return structure
calculate_complexity¶
Calculate complexity metrics for Go code.
Calculates: - Cyclomatic complexity - Cognitive complexity - Error handling complexity - Concurrency complexity - Test coverage indicators
| PARAMETER | DESCRIPTION |
|---|---|
content | Go source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
ComplexityMetrics | ComplexityMetrics object with calculated metrics |
Source code in tenets/core/analysis/implementations/go_analyzer.py
def calculate_complexity(self, content: str, file_path: Path) -> ComplexityMetrics:
"""Calculate complexity metrics for Go code.
Calculates:
- Cyclomatic complexity
- Cognitive complexity
- Error handling complexity
- Concurrency complexity
- Test coverage indicators
Args:
content: Go source code
file_path: Path to the file being analyzed
Returns:
ComplexityMetrics object with calculated metrics
"""
metrics = ComplexityMetrics()
# Calculate cyclomatic complexity
complexity = 1
decision_keywords = [
r"\bif\b",
r"\belse\s+if\b",
r"\belse\b",
r"\bfor\b",
r"\bswitch\b",
r"\bcase\b",
r"\bselect\b",
r"\bdefault\b",
r"\b&&\b",
r"\|\|",
]
for keyword in decision_keywords:
complexity += len(re.findall(keyword, content))
# Add complexity for range loops
complexity += len(re.findall(r"\bfor\s+\w+\s*:=\s*range\b", content))
metrics.cyclomatic = complexity
# Calculate cognitive complexity
cognitive = 0
nesting_level = 0
max_nesting = 0
lines = content.split("\n")
for line in lines:
# Skip comments
if line.strip().startswith("//"):
continue
# Track nesting
opening_braces = line.count("{")
closing_braces = line.count("}")
nesting_level += opening_braces - closing_braces
max_nesting = max(max_nesting, nesting_level)
# Control structures with nesting penalty
control_patterns = [
(r"\bif\b", 1),
(r"\bfor\b", 1),
(r"\bswitch\b", 1),
(r"\bselect\b", 2), # Higher weight for select
(r"\bcase\b", 0.5),
(r"\belse\s+if\b", 1),
(r"\belse\b", 0),
]
for pattern, weight in control_patterns:
if re.search(pattern, line):
cognitive += weight * (1 + max(0, nesting_level - 1))
# Error handling complexity
if "err != nil" in line:
cognitive += 1
metrics.error_handling_count = getattr(metrics, "error_handling_count", 0) + 1
# Panic/recover complexity
if re.search(r"\bpanic\b|\brecover\b", line):
cognitive += 2
metrics.cognitive = cognitive
metrics.max_depth = max_nesting
# Count code elements
metrics.line_count = len(lines)
metrics.code_lines = self._count_code_lines(content)
metrics.comment_lines = self._count_comment_lines(content)
metrics.comment_ratio = (
metrics.comment_lines / metrics.line_count if metrics.line_count > 0 else 0
)
# Count functions and methods
metrics.function_count = len(
re.findall(r"^func\s+(?:\([^)]+\)\s+)?\w+\s*\(", content, re.MULTILINE)
)
# Count structs and interfaces
metrics.struct_count = len(re.findall(r"^type\s+\w+\s+struct\s*\{", content, re.MULTILINE))
metrics.interface_count = len(
re.findall(r"^type\s+\w+\s+interface\s*\{", content, re.MULTILINE)
)
# Concurrency metrics
metrics.goroutines_count = len(re.findall(r"\bgo\s+\w+", content))
metrics.channels_count = len(re.findall(r"chan\s+\w+", content))
metrics.select_statements = len(re.findall(r"\bselect\s*\{", content))
metrics.mutex_usage = len(re.findall(r"sync\.(?:Mutex|RWMutex)", content))
# Error handling metrics
metrics.error_checks = len(re.findall(r"if\s+err\s*!=\s*nil", content))
metrics.error_returns = len(re.findall(r"return\s+.*err", content))
# Test metrics (if test file)
if file_path.name.endswith("_test.go"):
metrics.test_count = len(re.findall(r"^func\s+Test\w+\s*\(", content, re.MULTILINE))
metrics.benchmark_count = len(
re.findall(r"^func\s+Benchmark\w+\s*\(", content, re.MULTILINE)
)
metrics.example_count = len(
re.findall(r"^func\s+Example\w*\s*\(", content, re.MULTILINE)
)
# Calculate maintainability index
import math
if metrics.code_lines > 0:
# Adjusted for Go's error handling patterns
error_factor = max(0, 1 - (metrics.error_checks / metrics.code_lines))
mi = (
171
- 5.2 * math.log(max(1, complexity))
- 0.23 * complexity
- 16.2 * math.log(metrics.code_lines)
+ 20 * error_factor
) # Bonus for proper error handling
metrics.maintainability_index = max(0, min(100, mi))
return metrics
HTMLAnalyzer¶
Bases: LanguageAnalyzer
HTML code analyzer with modern web framework support.
Provides comprehensive analysis for HTML files including: - HTML5 semantic elements - CSS and JavaScript imports - Meta tags and SEO elements - Forms and input validation - Accessibility features (ARIA, alt text, etc.) - Web components and custom elements - Framework-specific patterns (React, Vue, Angular) - Microdata and structured data - DOM complexity and nesting depth - Performance hints (lazy loading, async/defer scripts) - Security considerations (CSP, integrity checks)
Supports HTML5 and modern web development practices.
Initialize the HTML analyzer with logger.
Source code in tenets/core/analysis/implementations/html_analyzer.py
extract_imports¶
Extract external resource imports from HTML.
Handles: - tags for CSS -